Skip to content

Instantly share code, notes, and snippets.

@giesse
Created August 16, 2020 18:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giesse/3ebac89c91ec8cbb06420f62d6b9dff2 to your computer and use it in GitHub Desktop.
Save giesse/3ebac89c91ec8cbb06420f62d6b9dff2 to your computer and use it in GitHub Desktop.
prin = function(text) {
sys.print(text);
return text;
};
print = function(text) {
sys.print(text + "\n");
return text;
};
jsProbe = function(value) {
print(sys.inspect(value));
return value;
};
lengthOfArray = function(arr) {
return arr.length;
};
insertArray = function(arr, pos, value) {
return arr.splice(pos, 0, value);
};
clearArray = function(arr, pos) {
return arr.splice(pos);
};
sliceArray = function(arr, begin, end) {
return arr.slice(begin, end);
};
copyArray = function(arr, begin) {
return arr.slice(begin);
};
concat = function(arr1, arr2) {
return arr1.concat(arr2);
};
insertArray2 = function(arr1, pos, arr2) {
return arr1.splice.apply(arr1, concat([pos, 0], arr2));
};
concat3 = function(arr1, arr2, arr3) {
return arr1.concat(arr2, arr3);
};
nameToJs = function(name) {
switch (name) {
case "arguments":
return "_arguments";
case "do":
return "_do";
case "json":
return "JSON";
case "case":
return "_case";
case "try":
return "_try";
case "throw":
return "_throw";
case "function":
return "_function";
case "if":
return "_if";
case "while":
return "_while";
case "switch":
return "_switch";
case "true":
return "_true";
case "false":
return "_false";
case "catch":
return "_catch";
case "new":
return "_new";
default:
name = name.replace(/-(.)/g, function(match, chr) {
return chr.toUpperCase();
});
name = name.replace(/^(.)(.*)\?$/, function(match, chr, rest) {
return ("is" + chr.toUpperCase()) + rest;
});
return name.replace("!", "_type");
}
};
collectSetWords = function(setwords, block, deep) {
var value;
while (!isEmpty(block)) {
value = first(block);
switch (value.type.name) {
case "set-word!":
append(setwords, value);
break;
case "block!":
if (deep) {
collectSetWords(setwords, value, true);
}
break;
case "paren!":
if (deep) {
collectSetWords(setwords, value, true);
}
break;
}
block = skip(block, 1);
}
return setwords;
};
cloneArray = function(arr) {
return arr.concat();
};
popArray = function(arr) {
return arr.pop();
};
appendArray = function(arr, value) {
return arr.push(value);
};
execRe = function(str, re) {
return re.exec(str);
};
testRe = function(str, re) {
return re.test(str);
};
isEmptyArray = function(array) {
return 0 == lengthOfArray(array);
};
isTrue = function(value) {
switch (value.type.name) {
case "none!":
return false;
case "logic!":
return value.value;
default:
return true;
}
};
isDefaultEqual = function(value1, value2) {
return false;
};
defaultBind = function(words, context, copy, _new) {
return words;
};
_foreach = function(arr, fnc) {
return arr.forEach(fnc);
};
handleJsError = function(value) {
if (value instanceof Error) {
return make(error_type, {
category: "Internal",
id: "js-error",
message: "Javascript error",
args: make(string_type, value.message),
stack: value.stack ? make(string_type, value.stack) : null
});
} else {
return value;
}
};
cloneObject = function(obj) {
return {
__proto__: obj
};
};
limitString = function(string, limit) {
if ((limit == null) || (limit >= lengthOfArray(string))) {
return string;
} else if (limit > 3) {
return sliceArray(string, 0, limit - 3) + "...";
} else {
return sliceArray("...", 0, limit);
}
};
isWithinLimit = function(string, limit) {
return (limit == null) || (limit > lengthOfArray(string));
};
subtractLimit = function(string, limit) {
if (limit == null) {
return null;
} else {
return limit - lengthOfArray(string);
}
};
make = function(type, spec) {
return type.make(spec);
};
insert = function(series, value, only, newLine) {
return series.type.insert(series, value, only, newLine);
};
head = function(series) {
return series.type.head(series);
};
tail = function(series) {
return series.type.tail(series);
};
pick = function(series, index) {
return series.type.pick(series, index);
};
lengthOf = function(series) {
return series.type.lengthOf(series);
};
isEmpty = function(series) {
return 0 == lengthOf(series);
};
skip = function(series, amount) {
return series.type.skip(series, amount);
};
mold = function(value, only, flat, limit, indent) {
return value.type.mold(value, only, flat, limit, indent);
};
first = function(series) {
return pick(series, 0);
};
second = function(series) {
return pick(series, 1);
};
next = function(series) {
return skip(series, 1);
};
doStep = function(value, block) {
var result, arg2, op, err;
try {
var _tmp = value.type._do(value, block);
result = _tmp[0];
block = _tmp[1];
} catch (e) {
e = handleJsError(e);
if (e.type.name == "error!") {
insert(tail(e.stack), block, true, false);
}
throw e;
}
while ((op = isOperator(block))) {
block = skip(block, 1);
if (isEmpty(block)) {
err = make(error_type, {
category: "Script",
id: "missing-argument",
message: "Operator missing its second argument"
});
insert(tail(err.stack), skip(block, -2), true, false);
throw err;
}
arg2 = first(block);
var _tmp = arg2.type._do(arg2, block);
arg2 = _tmp[0];
block = _tmp[1];
result = doOp(op, result, arg2);
}
return [result, block];
};
getPath = function(value, selector) {
switch (selector.type.name) {
case "paren!":
selector = _do(selector);
break;
case "get-word!":
selector = get(selector, false);
break;
}
return value.type.getPath(value, selector);
};
setPath = function(value, selector, setTo) {
switch (selector.type.name) {
case "paren!":
selector = _do(selector);
break;
case "get-word!":
selector = get(selector, false);
break;
}
return value.type.setPath(value, selector, setTo);
};
bind = function(words, context, copy, _new) {
return words.type.bind(words, context, copy, _new);
};
isEqual = function(val1, val2) {
var obj;
if ((obj = val1.type[val2.type.name])) {
return obj.isEqual(val1, val2);
} else {
return val1.type.isEqual(val1, val2);
}
};
probe = function(value, limit, indent) {
return print(mold(value, false, false, limit, indent));
};
datatype_type = null;
datatypes = [];
datatype_type = {
type: datatype_type,
name: "datatype!",
mold: function(type, only, flat, limit, indent) {
return limitString(type.name, limit);
},
_do: function(type, block) {
return [type, skip(block, 1)];
},
bind: defaultBind,
topazMake: function(ignored) {
return error({
category: "Internal",
id: "not-implemented",
message: "Cannot make datatypes (yet)"
});
},
compile: function(type, block) {
return [astValue(type), skip(block, 1)];
},
isEqual: function(type, value) {
return false;
}
};
datatypes.push(datatype_type);
datatype_type["datatype!"] = {
isEqual: function(type1, type2) {
return type1.name == type2.name;
}
};
datatype_type.type = datatype_type;
compareBlocks = function(block1, block2) {
var pos1, pos2, len;
len = lengthOf(block1);
if (len == lengthOf(block2)) {
pos1 = block1.pos;
pos2 = block2.pos;
len = len + pos1;
while ((pos1 < len) && isEqual(block1.values[pos1], block2.values[pos2])) {
pos1 = pos1 + 1;
pos2 = pos2 + 1;
}
return pos1 == len;
} else {
return false;
}
};
insertBlock = function(block, value, only, newLine) {
var tmp;
if (!only && isInsertAsBlock(value)) {
insertArray2(block.values, block.pos, (tmp = copyArray(value.values, value.pos)));
insertArray2(block.newlines, block.pos, copyArray(value.newlines, value.pos));
return makeBlock(block.type, {
values: block.values,
pos: block.pos + lengthOfArray(tmp),
newlines: block.newlines
});
} else {
insertArray(block.values, block.pos, value);
insertArray(block.newlines, block.pos, newLine);
return makeBlock(block.type, {
values: block.values,
pos: block.pos + 1,
newlines: block.newlines
});
}
};
skipBlock = function(block, amount) {
return atBlock(block, block.pos + amount);
};
headBlock = function(block) {
return makeBlock(block.type, {
values: block.values,
newlines: block.newlines
});
};
tailBlock = function(block) {
return makeBlock(block.type, {
values: block.values,
pos: lengthOfArray(block.values),
newlines: block.newlines
});
};
pickBlock = function(block, pos) {
return block.values[block.pos + pos] || make(none_type, null);
};
lengthOfBlock = function(block) {
return lengthOfArray(block.values) - block.pos;
};
bindBlock = function(words, context, copy, _new) {
if (copy) {
words = copyBlock(words.type, words);
}
bindValues(words.values, words.pos, context, copy, _new);
return words;
};
sliceBlock = function(start, endOrLength) {
var end;
if (isAnyBlock(endOrLength)) {
end = endOrLength.pos;
} else {
end = start.pos + endOrLength.number;
}
return makeBlock(start.type, {
values: sliceArray(start.values, start.pos, end),
newlines: sliceArray(start.newlines, start.pos, end)
});
};
clearBlock = function(series) {
clearArray(series.values, series.pos);
clearArray(series.newlines, series.pos);
return series;
};
block_type = {
type: datatype_type,
name: "block!",
make: function(_arguments) {
return makeBlock(block_type, _arguments);
},
topazMake: function(value) {
return topazMakeBlock(block_type, value);
},
insert: insertBlock,
head: headBlock,
tail: tailBlock,
skip: skipBlock,
pick: pickBlock,
lengthOf: lengthOfBlock,
mold: function(block, only, flat, limit, indent) {
return moldValues("[", block.newlines[lengthOfArray(block.values)] ? (("\n" + indent) + "]") : "]", " ", flat, flat ? " " : (("\n" + indent) + (only ? "" : " ")), flat ? "" : (indent + (only ? "" : " ")), limit, only, block.values, block.newlines, block.pos);
},
_do: function(block, container) {
return [block, skip(container, 1)];
},
bind: bindBlock,
compile: function(block, container) {
return [astValue(block), skip(container, 1)];
},
isEqual: isDefaultEqual,
copy: function(value) {
return copyBlock(block_type, value);
},
slice: sliceBlock,
getPath: function(block, selector) {
if (!isNumber(selector)) {
error({
category: "Script",
id: "invalid-path",
message: "Invalid path value",
args: selector
});
}
return pickBlock(block, selector.number);
},
setPath: function(block, selector, setTo) {
if (!isNumber(selector)) {
error({
category: "Script",
id: "invalid-path",
message: "Invalid path value",
args: selector
});
}
return (block.values[block.pos + selector.number] = setTo);
},
clear: clearBlock
};
datatypes.push(block_type);
block_type["block!"] = {
isEqual: compareBlocks
};
block_type["paren!"] = {
isEqual: compareBlocks
};
makeBlock = function(type, args) {
if (!args) {
args = {};
}
return {
type: type,
values: args.values || [],
pos: args.pos || 0,
newlines: args.newlines || []
};
};
copyBlock = function(type, value) {
return makeBlock(type, {
values: copyArray(value.values, value.pos),
newlines: copyArray(value.newlines, value.pos)
});
};
topazMakeBlock = function(type, value) {
switch (value.type.name) {
case "block!":
return copyBlock(type, value);
case "paren!":
return copyBlock(type, value);
case "path!":
return pathToBlock(type, value);
case "lit-path!":
return pathToBlock(type, value);
case "set-path!":
return pathToBlock(type, value);
case "string!":
return loadRaw(type, value);
default:
return makeBlock(type, null);
}
};
bindValues = function(values, pos, context, copy, _new) {
while (pos < lengthOfArray(values)) {
values[pos] = bind(values[pos], context, copy, _new);
pos = pos + 1;
}
return null;
};
setNewLine = function(block, isNewline) {
return (block.newlines[block.pos] = isNewline);
};
append = function(block, value) {
return head(insert(tail(block), value, false, false));
};
_foreachBlk = function(block, fnc) {
var pos;
pos = block.pos;
while (pos < lengthOfArray(block.values)) {
fnc(block.values[pos], pos);
pos = pos + 1;
}
return null;
};
atBlock = function(block, pos) {
var len;
if (pos > (len = lengthOfArray(block.values))) {
pos = len;
} else if (pos < 0) {
pos = 0;
}
return makeBlock(block.type, {
values: block.values,
pos: pos,
newlines: block.newlines
});
};
moldStep = function(result, value, flat, limit, indent, sep) {
return (result + sep) + mold(value, false, flat, subtractLimit(result, limit), indent);
};
moldValues = function(open, close, sep, flat, nlsep, indent, limit, only, values, newlines, pos) {
var result, value, len;
result = only ? "" : open;
if (isWithinLimit(result, limit) && (pos < lengthOfArray(values))) {
result = moldStep(result, values[pos], flat, limit, indent, (!only && newlines[pos]) ? nlsep : "");
pos = pos + 1;
while (isWithinLimit(result, limit) && (pos < lengthOfArray(values))) {
result = moldStep(result, values[pos], flat, limit, indent, newlines[pos] ? nlsep : sep);
pos = pos + 1;
}
}
if (pos == lengthOfArray(values)) {
result = result + (only ? "" : close);
} else {
result = result + "...";
}
return limitString(result, limit);
};
parseBlock = function(text) {
var values;
if ("[" == first(text)) {
var _tmp = parseValues(make(block_type, null), skip(text, 1));
values = _tmp[0];
text = _tmp[1];
if ("]" != first(text)) {
error({
category: "Syntax",
id: "load-error",
message: "Missing ]",
stack: text
});
}
return [values, skip(text, 1)];
}
};
paren_type = {
type: datatype_type,
name: "paren!",
make: function(_arguments) {
return makeBlock(paren_type, _arguments);
},
topazMake: function(value) {
return topazMakeBlock(paren_type, value);
},
insert: insertBlock,
head: headBlock,
tail: tailBlock,
skip: skipBlock,
pick: pickBlock,
lengthOf: lengthOfBlock,
mold: function(block, only, flat, limit, indent) {
return moldValues("(", block.newlines[lengthOfArray(block.values)] ? (("\n" + indent) + ")") : ")", " ", flat, flat ? " " : (("\n" + indent) + " "), flat ? "" : (indent + " "), limit, false, block.values, block.newlines, block.pos);
},
_do: function(paren, block) {
return [_do(paren), skip(block, 1)];
},
bind: bindBlock,
compile: function(paren, block) {
return [astParen(compile(paren)), skip(block, 1)];
},
isEqual: isDefaultEqual,
copy: function(value) {
return copyBlock(paren_type, value);
},
slice: sliceBlock,
clear: clearBlock
};
datatypes.push(paren_type);
paren_type["paren!"] = {
isEqual: compareBlocks
};
paren_type["block!"] = {
isEqual: compareBlocks
};
parseParen = function(text) {
var values;
if ("(" == first(text)) {
var _tmp = parseValues(make(paren_type, null), skip(text, 1));
values = _tmp[0];
text = _tmp[1];
if (")" != first(text)) {
error({
category: "Syntax",
id: "load-error",
message: "Missing )",
stack: text
});
}
return [values, skip(text, 1)];
}
};
insertPath = function(path, value, only, newLine) {
var tmp;
if (!only && isAnyBlock(value)) {
insertArray2(path.values, path.pos, (tmp = copyArray(value.values, value.pos)));
return makePath(path.type, {
values: path.values,
pos: path.pos + lengthOfArray(tmp)
});
} else {
insertArray(path.values, path.pos, value);
return makePath(path.type, {
values: path.values,
pos: path.pos + 1
});
}
};
skipPath = function(path, amount) {
var pos, len;
pos = path.pos + amount;
if (pos > (len = lengthOfArray(path.values))) {
pos = len;
} else if (pos < 0) {
pos = 0;
}
return makePath(path.type, {
values: path.values,
pos: pos
});
};
headPath = function(path) {
return makePath(path.type, {
values: path.values
});
};
tailPath = function(path) {
return makePath(path.type, {
values: path.values,
pos: lengthOfArray(path.values)
});
};
slicePath = function(start, endOrLength) {
var end;
if (isAnyBlock(endOrLength)) {
end = endOrLength.pos;
} else {
end = start.pos + endOrLength.number;
}
return makePath(start.type, {
values: sliceArray(start.values, start.pos, end)
});
};
path_type = {
type: datatype_type,
name: "path!",
make: function(_arguments) {
return makePath(path_type, _arguments);
},
topazMake: function(value) {
return topazMakePath(path_type, value);
},
insert: insertPath,
head: headPath,
tail: tailPath,
skip: skipPath,
pick: pickBlock,
lengthOf: lengthOfBlock,
mold: function(block, only, flat, limit, indent) {
return moldValues("", "", "/", true, "/", "", limit, false, block.values, [], block.pos);
},
_do: function(path, block) {
var value;
if (isEmpty(path)) {
error({
category: "Script",
id: "invalid-path",
message: "Empty PATH! value"
});
}
value = get(first(path), false);
path = skip(path, 1);
while (!isEmpty(path)) {
value = getPath(value, first(path));
path = skip(path, 1);
}
if (isWordActive(value)) {
return value.type._do(value, block);
} else {
return [value, skip(block, 1)];
}
},
bind: bindBlock,
compile: function(path, block) {
var value, expr;
if (isEmpty(path)) {
error({
category: "Compilation",
id: "invalid-path",
message: "Empty PATH! value"
});
}
value = get(first(path), true);
if (value && isWordActive(value)) {
path = skip(path, 1);
while (!isEmpty(path)) {
value = getPath(value, first(path));
path = skip(path, 1);
}
return value.type.compile(value, block);
} else {
expr = astGet(first(path));
path = skip(path, 1);
while (!isEmpty(path)) {
expr = astGetPath(expr, first(path));
path = skip(path, 1);
}
return [expr, skip(block, 1)];
}
},
isEqual: isDefaultEqual,
copy: function(value) {
return copyPath(path_type, value);
},
slice: slicePath
};
datatypes.push(path_type);
path_type["path!"] = {
isEqual: compareBlocks
};
path_type["set-path!"] = {
isEqual: compareBlocks
};
path_type["lit-path!"] = {
isEqual: compareBlocks
};
makePath = function(type, args) {
if (!args) {
args = {};
}
return {
type: type,
values: args.values || [],
pos: args.pos || 0
};
};
topazMakePath = function(type, value) {
switch (value.type.name) {
case "block!":
return copyPath(type, value);
case "paren!":
return copyPath(type, value);
case "path!":
return copyPath(type, value);
case "lit-path!":
return copyPath(type, value);
case "set-path!":
return copyPath(type, value);
default:
return makePath(type, null);
}
};
copyPath = function(type, value) {
return makePath(type, {
values: value.values.slice(value.pos)
});
};
makeNewlines = function(values) {
var newlines;
newlines = [];
_foreach(values, function(item) {
return appendArray(newlines, false);
});
return newlines;
};
pathToBlock = function(type, value) {
var values;
values = value.values.slice(value.pos);
return makeBlock(type, {
values: values,
newlines: makeNewlines(values)
});
};
parsePath = function(text) {
var path, value;
if ((value = parseWordChars(text)) && ("/" == pick(text, lengthOfArray(value)))) {
path = make(path_type, null);
path = insert(path, make(word_type, value), false, false);
text = skip(text, lengthOfArray(value));
while (!isEmpty(text) && ("/" == first(text))) {
var _tmp = parsePathElement(skip(text, 1));
value = _tmp[0];
text = _tmp[1];
if (value) {
path = insert(path, value, true, false);
} else {
error({
category: "Syntax",
id: "load-error",
message: "Parse error",
stack: text
});
}
}
return [head(path), text];
}
};
litPath_type = {
type: datatype_type,
name: "lit-path!",
make: function(_arguments) {
return makePath(litPath_type, _arguments);
},
topazMake: function(value) {
return topazMakePath(litPath_type, value);
},
insert: insertPath,
head: headPath,
tail: tailPath,
skip: skipPath,
pick: pickBlock,
lengthOf: lengthOfBlock,
mold: function(block, only, flat, limit, indent) {
return moldValues("'", "", "/", true, "/", "", limit, false, block.values, [], block.pos);
},
_do: function(path, block) {
return [copyPath(path_type, path), skip(block, 1)];
},
bind: bindBlock,
compile: function(path, block) {
return [astValue({
type: path_type,
values: path.values,
pos: path.pos
}), skip(block, 1)];
},
isEqual: isDefaultEqual,
copy: function(value) {
return copyPath(litPath_type, value);
},
slice: slicePath
};
datatypes.push(litPath_type);
litPath_type["path!"] = {
isEqual: compareBlocks
};
litPath_type["set-path!"] = {
isEqual: compareBlocks
};
litPath_type["lit-path!"] = {
isEqual: compareBlocks
};
parseLitPath = function(text) {
var path, value;
if (("'" == first(text)) && (value = parseWordChars(skip(text, 1))) && ("/" == pick(text, 1 + lengthOfArray(value)))) {
path = make(litPath_type, null);
path = insert(path, make(word_type, value), false, false);
text = skip(text, 1 + lengthOfArray(value));
while (!isEmpty(text) && ("/" == first(text))) {
var _tmp = parsePathElement(skip(text, 1));
value = _tmp[0];
text = _tmp[1];
if (value) {
path = insert(path, value, true, false);
} else {
error({
category: "Syntax",
id: "load-error",
message: "Parse error",
stack: text
});
}
}
return [head(path), text];
}
};
setPath_type = {
type: datatype_type,
name: "set-path!",
make: function(_arguments) {
return makePath(setPath_type, _arguments);
},
topazMake: function(value) {
return topazMakePath(setPath_type, value);
},
insert: insertPath,
head: headPath,
tail: tailPath,
skip: skipPath,
pick: pickBlock,
lengthOf: lengthOfBlock,
mold: function(block, only, flat, limit, indent) {
return moldValues("", ":", "/", true, "/", "", limit, false, block.values, [], block.pos);
},
_do: function(path, block) {
var value, setTo;
if (2 > lengthOf(path)) {
error({
category: "Script",
id: "invalid-path",
message: "SET-PATH! with less than two values"
});
}
value = get(first(path), false);
path = skip(path, 1);
while (1 < lengthOf(path)) {
value = getPath(value, first(path));
path = skip(path, 1);
}
block = skip(block, 1);
if (isEmpty(block)) {
error({
category: "Script",
id: "missing-argument",
message: "SET-PATH! needs a value"
});
}
var _tmp = doStep(first(block), block);
setTo = _tmp[0];
block = _tmp[1];
setPath(value, first(path), setTo);
return [setTo, block];
},
bind: bindBlock,
compile: function(path, block) {
var expr, setTo;
if (2 > lengthOf(path)) {
error({
category: "Compilation",
id: "invalid-path",
message: "SET-PATH! with less than two values"
});
}
expr = astGet(first(path));
path = skip(path, 1);
while (1 < lengthOf(path)) {
expr = astGetPath(expr, first(path));
path = skip(path, 1);
}
block = skip(block, 1);
if (isEmpty(block)) {
error({
category: "Compilation",
id: "missing-argument",
message: "SET-PATH! needs a value"
});
}
var _tmp = compileStep(block);
setTo = _tmp[0];
block = _tmp[1];
return [astSetPath(expr, first(path), setTo), block];
},
isEqual: isDefaultEqual,
copy: function(value) {
return copyPath(setPath_type, value);
},
slice: slicePath
};
datatypes.push(setPath_type);
setPath_type["path!"] = {
isEqual: compareBlocks
};
setPath_type["set-path!"] = {
isEqual: compareBlocks
};
setPath_type["lit-path!"] = {
isEqual: compareBlocks
};
parseSetPath = function(text) {
var path, value;
if ((value = parseWordChars(text)) && ("/" == pick(text, lengthOfArray(value)))) {
path = make(setPath_type, null);
path = insert(path, make(word_type, value), false, false);
text = skip(text, lengthOfArray(value));
while (!isEmpty(text) && ("/" == first(text))) {
var _tmp = parsePathElement(skip(text, 1));
value = _tmp[0];
text = _tmp[1];
if (value) {
path = insert(path, value, true, false);
} else {
error({
category: "Syntax",
id: "load-error",
message: "Parse error",
stack: text
});
}
}
if (!isEmpty(text) && (":" == first(text))) {
return [head(path), skip(text, 1)];
}
}
};
compareWords = function(word1, word2) {
return word1.word == word2.word;
};
word_type = {
type: datatype_type,
name: "word!",
make: function(_arguments) {
return makeWord(word_type, _arguments, null);
},
topazMake: function(value) {
return topazMakeWord(word_type, value);
},
mold: function(word, only, flat, limit, indent) {
return limitString(word.word, limit);
},
_do: function(word, block) {
var value;
value = get(word, false);
if (isWordActive(value)) {
return value.type._do(value, block);
} else {
return [value, skip(block, 1)];
}
},
bind: function(word, context, copy, _new) {
return bindWord(context, word, _new);
},
compile: function(word, block) {
var value;
value = get(word, true);
if (value && isCompilerWordActive(value)) {
return value.type.compile(value, block);
} else {
return [astGet(word), skip(block, 1)];
}
},
isEqual: isDefaultEqual
};
datatypes.push(word_type);
word_type["word!"] = {
isEqual: compareWords
};
word_type["get-word!"] = {
isEqual: compareWords
};
word_type["set-word!"] = {
isEqual: compareWords
};
word_type["lit-word!"] = {
isEqual: compareWords
};
makeWord = function(type, word, args) {
if (!args) {
args = {};
}
return {
type: type,
word: word,
context: args.context,
offset: args.offset
};
};
topazMakeWord = function(type, value) {
switch (value.type.name) {
case "string!":
return makeWord(type, toJsString(value), null);
case "word!":
return convertWord(value, type);
case "set-word!":
return convertWord(value, type);
case "get-word!":
return convertWord(value, type);
case "lit-word!":
return convertWord(value, type);
case "datatype!":
return bindWord(systemWords, makeWord(type, value.name, null), true);
default:
return error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE " + type.name.toUpperCase(),
args: value
});
}
};
convertWord = function(word, type) {
return makeWord(type, word.word, {
context: word.context,
offset: word.offset
});
};
get = function(word, any) {
var value;
if (!word.context) {
error({
category: "Script",
id: "no-context",
message: "Word has no context",
args: word
});
}
value = word.context.values[word.offset];
if (!any && !value) {
error({
category: "Script",
id: "no-value",
message: "Word has no value",
args: word
});
}
return value;
};
parseWord = function(text) {
var w;
if ((w = parseWordChars(text))) {
return [make(word_type, w), skip(text, lengthOfArray(w))];
}
};
getWord_type = {
type: datatype_type,
name: "get-word!",
make: function(_arguments) {
return makeWord(getWord_type, _arguments, null);
},
topazMake: function(value) {
return topazMakeWord(getWord_type, value);
},
mold: function(word, only, flat, limit, indent) {
return limitString(":" + word.word, limit);
},
_do: function(word, block) {
return [get(word, false), skip(block, 1)];
},
bind: function(word, context, copy, _new) {
return bindWord(context, word, _new);
},
compile: function(word, block) {
return [astGet(word), skip(block, 1)];
},
isEqual: isDefaultEqual
};
datatypes.push(getWord_type);
getWord_type["word!"] = {
isEqual: compareWords
};
getWord_type["get-word!"] = {
isEqual: compareWords
};
getWord_type["set-word!"] = {
isEqual: compareWords
};
getWord_type["lit-word!"] = {
isEqual: compareWords
};
parseGetWord = function(text) {
var w;
if ((":" == first(text)) && (w = parseWordChars(skip(text, 1)))) {
return [make(getWord_type, w), skip(text, 1 + lengthOfArray(w))];
}
};
litWord_type = {
type: datatype_type,
name: "lit-word!",
make: function(_arguments) {
return makeWord(litWord_type, _arguments, null);
},
topazMake: function(value) {
return topazMakeWord(litWord_type, value);
},
mold: function(word, only, flat, limit, indent) {
return limitString("'" + word.word, limit);
},
_do: function(word, block) {
return [convertWord(word, word_type), skip(block, 1)];
},
bind: function(word, context, copy, _new) {
return bindWord(context, word, _new);
},
compile: function(word, block) {
return [astValue(convertWord(word, word_type)), skip(block, 1)];
},
isEqual: isDefaultEqual
};
datatypes.push(litWord_type);
litWord_type["word!"] = {
isEqual: compareWords
};
litWord_type["get-word!"] = {
isEqual: compareWords
};
litWord_type["set-word!"] = {
isEqual: compareWords
};
litWord_type["lit-word!"] = {
isEqual: compareWords
};
parseLitWord = function(text) {
var w;
if (("'" == first(text)) && (w = parseWordChars(skip(text, 1)))) {
return [make(litWord_type, w), skip(text, 1 + lengthOfArray(w))];
}
};
setWord_type = {
type: datatype_type,
name: "set-word!",
make: function(_arguments) {
return makeWord(setWord_type, _arguments, null);
},
topazMake: function(value) {
return topazMakeWord(setWord_type, value);
},
mold: function(word, only, flat, limit, indent) {
return limitString(word.word + ":", limit);
},
_do: function(word, block) {
var setTo;
block = skip(block, 1);
if (isEmpty(block)) {
error({
category: "Script",
id: "missing-argument",
message: "SET-WORD! needs a value"
});
}
var _tmp = doStep(first(block), block);
setTo = _tmp[0];
block = _tmp[1];
setWord(word, setTo);
return [setTo, block];
},
bind: function(word, context, copy, _new) {
return bindWord(context, word, _new);
},
compile: function(word, block) {
var setTo;
block = skip(block, 1);
if (isEmpty(block)) {
error({
category: "Compilation",
id: "missing-argument",
message: "SET-WORD! needs a value"
});
}
var _tmp = compileStep(block);
setTo = _tmp[0];
block = _tmp[1];
return [astSet(word, setTo), block];
},
isEqual: isDefaultEqual
};
datatypes.push(setWord_type);
setWord_type["word!"] = {
isEqual: compareWords
};
setWord_type["get-word!"] = {
isEqual: compareWords
};
setWord_type["set-word!"] = {
isEqual: compareWords
};
setWord_type["lit-word!"] = {
isEqual: compareWords
};
setWord = function(word, value) {
if (!word.context) {
error({
category: "Script",
id: "no-context",
message: "Word has no context",
args: word
});
}
word.context.values[word.offset] = value;
return value;
};
parseSetWord = function(text) {
var w;
if ((w = parseWordChars(text)) && (":" == pick(text, lengthOfArray(w)))) {
return [make(setWord_type, w), skip(text, 1 + lengthOfArray(w))];
}
};
context_type = {
type: datatype_type,
name: "context!",
make: function(_arguments) {
var ctx;
if (!_arguments) {
_arguments = {};
}
ctx = {
type: context_type,
words: _arguments.words || [],
values: _arguments.values || [],
compilerValues: []
};
setInContext(ctx, make(word_type, "this-context"), ctx);
return ctx;
},
topazMake: function(code) {
var ctx;
switch (code.type.name) {
case "block!":
ctx = make(context_type, null);
bind(collectSetWords(make(block_type, null), code, false), ctx, false, true);
_do(bind(code, ctx, false, false));
return ctx;
default:
return error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE CONTEXT!",
args: code
});
}
},
mold: function(context, only, flat, limit, indent) {
return moldWordsAndValues("context [", flat ? "]" : (("\n" + indent) + "]"), limit, copyArray(context.words, 1), function(word) {
return context.values[getWordOffset(context, word)];
}, flat ? " " : (("\n" + indent) + " "), flat, indent + " ");
},
_do: function(context, block) {
return [context, skip(block, 1)];
},
bind: defaultBind,
compile: function(context, block) {
return [astValue(context), skip(block, 1)];
},
getPath: function(context, selector) {
var offset;
if (!isAnyWord(selector) || (0 > (offset = getWordOffset(context, selector.word)))) {
error({
category: "Script",
id: "invalid-path",
message: "Invalid path value",
args: selector
});
}
return context.values[offset];
},
setPath: function(context, selector, setTo) {
var offset;
if (!isAnyWord(selector) || (0 > (offset = getWordOffset(context, selector.word)))) {
error({
category: "Script",
id: "invalid-path",
message: "Invalid path value",
args: selector
});
}
return (context.values[offset] = setTo);
},
isEqual: isDefaultEqual,
isIn: function(ctx, word) {
var offset;
if (!isAnyWord(word)) {
error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for IN? CONTEXT!",
args: word
});
}
if (0 > (offset = getWordOffset(ctx, word.word))) {
return make(none_type, null);
} else {
return makeWord(word.type, word.word, {
context: ctx,
offset: offset
});
}
}
};
datatypes.push(context_type);
getWordOffset = function(context, word) {
return context.words.indexOf(word);
};
addWord = function(context, word, value) {
var l;
l = lengthOfArray(context.words);
context.words[l] = word.word;
context.values[l] = value;
return l;
};
bindWord = function(context, word, isAdd) {
var offset;
offset = getWordOffset(context, word.word);
if (isAdd && (offset < 0)) {
offset = addWord(context, word, null);
}
if (offset >= 0) {
return makeWord(word.type, word.word, {
context: context,
offset: offset
});
} else {
return word;
}
};
setInContext = function(context, word, setTo) {
var offset;
if (0 > (offset = getWordOffset(context, word.word))) {
return addWord(context, word, setTo);
} else {
return (context.values[offset] = setTo);
}
};
moldWordsAndValues = function(open, close, limit, words, getWordF, sep, flat, indent) {
var result, i, value;
result = open;
i = 0;
while (isWithinLimit(result, limit) && (i < lengthOfArray(words))) {
result = ((result + sep) + words[i]) + ": ";
if (isWithinLimit(result, limit)) {
if ((value = getWordF(words[i]))) {
result = result + mold(isWord(value) ? make(litWord_type, value.word) : value, false, flat, subtractLimit(result, limit), indent);
} else {
result = result + "<unset>";
}
}
i = i + 1;
}
if (isWithinLimit(result, limit)) {
result = result + close;
}
return limitString(result, limit);
};
contextToObject = function(context) {
var words, map;
map = {};
_foreach((words = copyArray(context.words, 1)), function(word, pos) {
return (map[word] = context.values[pos + 1]);
});
return make(object_type, {
words: words,
map: map
});
};
getCompilerValue = function(word) {
if (word.context) {
return word.context.compilerValues[word.offset] || make(none_type, null);
} else {
return make(none_type, null);
}
};
setCompilerValue = function(word, value) {
if (!word.context) {
error({
category: "Script",
id: "no-context",
message: "Word has no context",
args: word
});
}
word.context.compilerValues[word.offset] = value;
return value;
};
makeFuncObject = function(type, spec) {
return {
type: type,
argsList: [],
mandatory: 0,
args: {},
mode: "fixed",
flags: [],
spec: spec
};
};
mandatoryArgumentsOf = function(specObj) {
return sliceArray(specObj.argsList, 0, specObj.mandatory);
};
appendArgument = function(specObj, arg, spec) {
var name;
appendArray(specObj.argsList, arg);
if (!arg.isOptional) {
specObj.mandatory = specObj.mandatory + 1;
}
if (specObj.args[(name = argumentNameOf(arg))]) {
error({
category: "Script",
id: "invalid-spec",
message: "Duplicate function argument name",
args: arg.word,
stack: spec
});
}
return (specObj.args[name] = arg);
};
changeInvocation = function(func, mode) {
func = cloneObject(func);
func.mode = mode;
return func;
};
invocationModeOf = function(func) {
return func.mode;
};
insertFlag = function(func, flag) {
func = cloneObject(func);
func.flags = func.flags.concat(flag);
return func;
};
getArgumentObject = function(func, name) {
return func.args[name];
};
makeArgumentObject = function(word, isOpt) {
return {
word: word,
isOptional: isOpt,
types: null,
offset: 0
};
};
argumentNameOf = function(arg) {
return arg.word.word;
};
functionGetPath = function(func, selector) {
var arg;
if (!isWord(selector)) {
error({
category: "Script",
id: "invalid-path",
message: "Invalid path value",
args: selector
});
}
if (selector.word == "options") {
return changeInvocation(func, "named");
} else {
if (!(arg = getArgumentObject(func, selector.word))) {
error({
category: "Script",
id: "invalid-path",
message: "Function has no such argument",
args: selector
});
}
if (!arg.isOptional || (arg.types && !isInTypeset(arg.types, "logic!"))) {
error({
category: "Script",
id: "invalid-path",
message: "Argument cannot be used as a flag",
args: selector
});
}
return insertFlag(func, selector.word);
}
};
functionDo = function(func, block) {
var args;
args = func.type.prepareArguments(func);
var _tmp = collectFunctionArguments(func, args, block, "Script", make(logic_type, true), function(block) {
return doStep(first(block), block);
}, checkArgument);
block = _tmp[0];
args = _tmp[1];
return [func.type.call(func, args), block];
};
functionCompile = function(func, block, isWrap) {
var args, logicTrue, compArg;
args = func.type.prepareArguments(func);
logicTrue = astValue(make(logic_type, true));
func.type.compileDefaultArguments(func, args, isWrap);
if (isWrap) {
logicTrue = make(expression_type, logicTrue);
compArg = function(block) {
var expr;
var _tmp = compileStep(block);
expr = _tmp[0];
block = _tmp[1];
return [make(expression_type, expr), block];
};
} else {
compArg = compileStep;
}
var _tmp = collectFunctionArguments(func, args, block, "Compilation", logicTrue, compArg, function(arg, value, block) {
return null;
});
block = _tmp[0];
args = _tmp[1];
return [func.type.callCompile(func, args), block];
};
function_type = {
type: datatype_type,
name: "function!",
make: function(_arguments) {
var fnc;
fnc = makeFuncObject(function_type, _arguments[0]);
fnc.context = make(context_type, null);
fnc.body = _arguments[1];
parseFunctionSpec(fnc);
collectLocals(fnc);
bind(fnc.body, fnc.context, false, false);
fnc.defaultValues = fnc.context.values;
return fnc;
},
topazMake: function(block) {
if (!isBlock(block)) {
error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE FUNCTION!",
args: block
});
}
if (2 != lengthOf(block)) {
error({
category: "Script",
id: "invalid-spec",
message: "MAKE FUNCTION! requires a spec and a body, not",
args: block
});
}
if (!isBlock(first(block))) {
error({
category: "Script",
id: "invalid-spec",
message: "Function spec must be a BLOCK!"
});
}
if (!isBlock(second(block))) {
error({
category: "Script",
id: "invalid-spec",
message: "Function body must be a BLOCK!"
});
}
return function_type.make(block.values);
},
mold: function(func, only, flat, limit, indent) {
var result;
result = "func ";
if (isWithinLimit(result, limit)) {
result = (result + mold(func.spec, false, flat, subtractLimit(result, limit), indent)) + " ";
}
if (isWithinLimit(result, limit)) {
result = result + mold(func.body, false, flat, subtractLimit(result, limit), indent);
}
return limitString(result, limit);
},
_do: functionDo,
prepareArguments: function(func) {
return cloneArray(func.defaultValues);
},
call: function(func, args) {
var res, saved;
saved = func.context.values;
func.context.values = args;
try {
res = _do(func.body);
} catch (e) {
e = handleJsError(e);
if ((e.type.name == "return-value!") && (e.func == func)) {
res = e.value;
} else {
throw e;
}
}
func.context.values = saved;
return res;
},
bind: defaultBind,
compile: function(func, block) {
return functionCompile(func, block, true);
},
callCompile: function(func, args) {
var expr;
if (!isExpression((expr = function_type.call(func, args)))) {
error({
category: "Compilation",
id: "invalid-argument",
message: "Macro did not return EXPRESSION! value",
args: expr
});
}
return expr.expr;
},
isEqual: isDefaultEqual,
getPath: functionGetPath,
setArgument: function(func, args, arg, value) {
args[arg.offset] = value;
return args;
},
getArgument: function(func, args, arg) {
return args[arg.offset];
},
addArgument: function(func, arg, dflt) {
return (arg.offset = addWord(func.context, arg.word, dflt));
},
setReturn: function(func, spec) {
return setInContext(func.context, make(word_type, "return"), make(return_type, {
spec: spec,
func: func
}));
},
compileDefaultArguments: function(func, args, isWrap) {
return _foreach(args, function(value, pos) {
if ((pos > 0) && value) {
return (args[pos] = isWrap ? make(expression_type, astValue(value)) : astValue(value));
}
});
}
};
datatypes.push(function_type);
collectFunctionArguments = function(func, args, block, category, logicTrue, doArg, checkArg) {
var argsBlock;
_foreach(func.flags, function(flag) {
return (args = func.type.setArgument(func, args, getArgumentObject(func, flag), logicTrue));
});
switch (invocationModeOf(func)) {
case "fixed":
var _tmp = collectArguments(func, args, mandatoryArgumentsOf(func), skip(block, 1), category, doArg, checkArg);
block = _tmp[0];
args = _tmp[1];
break;
case "named":
if (isEmpty(next(block))) {
error({
category: category,
id: "missing-argument",
message: "Function is missing the argument spec"
});
}
argsBlock = second(block);
if (!isBlock(argsBlock)) {
error({
category: category,
id: "invalid-spec",
message: "Function argument spec must be a literal block, not",
args: argsBlock.type
});
}
args = parseNamedArguments(func, args, argsBlock, category, logicTrue, doArg, checkArg);
block = skip(block, 2);
break;
default:
error({
category: "Internal",
id: "failed-check",
message: "Something's wrong: function! do, mode not fixed or named"
});
}
return [block, args];
};
checkArgument = function(arg, value, block) {
if (arg.types && !isInTypeset(arg.types, value.type.name)) {
return invalidArgumentType("Script", arg, value, block);
}
};
collectArguments = function(func, args, list, block, category, doArg, checkArg) {
var value, origBlock;
_foreach(list, function(arg) {
if (isEmpty(block)) {
error({
category: category,
id: "missing-argument",
message: "Not enough arguments for function"
});
}
var _tmp = doArg((origBlock = block));
value = _tmp[0];
block = _tmp[1];
checkArg(arg, value, origBlock);
return (args = func.type.setArgument(func, args, arg, value));
});
return [block, args];
};
invalidArgumentType = function(category, arg, value, stack) {
var name;
name = argumentNameOf(arg);
return error({
category: category,
id: "invalid-argument",
message: (((("Invalid type for argument " + name.toUpperCase()) + ": ") + value.type.name) + " not in ") + mold(arg.types, true, false, null, ""),
args: value,
stack: stack
});
};
invalidArgumentName = function(category, word, stack) {
return error({
category: category,
id: "invalid-argument",
message: "Function does not take an argument with this name",
args: word,
stack: stack
});
};
parseNamedArguments = function(func, args, block, category, logicTrue, doArg, checkArg) {
var word, value, arg, origBlock;
while (!isEmpty(block)) {
word = first(block);
switch (word.type.name) {
case "word!":
if (!(arg = getArgumentObject(func, word.word))) {
invalidArgumentName(category, word, block);
}
block = next((origBlock = block));
value = logicTrue;
break;
case "set-word!":
if (!(arg = getArgumentObject(func, word.word))) {
invalidArgumentName(category, word, block);
}
var _tmp = doArg((origBlock = next(block)));
value = _tmp[0];
block = _tmp[1];
break;
default:
error({
category: category,
id: "invalid-spec",
message: "Invalid arguments spec value",
args: word,
stack: block
});
}
checkArg(arg, value, origBlock);
args = func.type.setArgument(func, args, arg, value);
}
_foreach(mandatoryArgumentsOf(func), function(arg) {
if (!func.type.getArgument(func, args, arg)) {
return error({
category: category,
id: "missing-argument",
message: "Missing mandatory argument",
args: arg.word,
stack: block
});
}
});
return args;
};
invalidSpecValue = function(value, spec) {
return error({
category: "Script",
id: "invalid-spec",
message: "Invalid function spec value",
args: value,
stack: spec
});
};
parseFunctionSpec = function(fnc) {
var spec;
spec = fnc.spec;
while (!isEmpty(spec)) {
value = first(spec);
switch (value.type.name) {
case "string!":
spec = next(spec);
break;
case "word!":
spec = parseArgumentSpec(fnc, spec, false);
break;
case "set-word!":
switch (value.word) {
case "options":
spec = parseFunctionOptions(fnc, next(spec));
break;
case "return":
spec = parseFunctionReturn(fnc, next(spec));
break;
default:
invalidSpecValue(value, spec);
}
break;
default:
invalidSpecValue(value, spec);
}
}
if (3 < fnc.mandatory) {
fnc.mode = "named";
}
return null;
};
parseArgumentSpec = function(fnc, spec, isOpt) {
var word, arg, value;
word = first(spec);
arg = makeArgumentObject(word, isOpt);
appendArgument(fnc, arg, spec);
if (isSetWord(word)) {
if (!isOpt) {
error({
category: "Internal",
id: "failed-check",
message: "Something's wrong: parse-argument-spec, set-word!, not optional",
args: word,
stack: spec
});
}
var _tmp = doStep(second(spec), next(spec));
value = _tmp[0];
spec = _tmp[1];
} else {
spec = next(spec);
value = isOpt ? make(none_type, null) : null;
}
fnc.type.addArgument(fnc, arg, value);
if (!isEmpty(spec)) {
value = first(spec);
if (isBlock(value)) {
spec = next(spec);
arg.types = typeset_type.topazMake(value);
}
}
return spec;
};
parseFunctionOptions = function(fnc, spec) {
var word;
while (!isEmpty(spec) && (word = first(spec)) && !(isSetWord(word) && (word.word == "return"))) {
switch (word.type.name) {
case "word!":
spec = parseArgumentSpec(fnc, spec, true);
break;
case "set-word!":
spec = parseArgumentSpec(fnc, spec, true);
break;
case "string!":
spec = next(spec);
break;
default:
invalidSpecValue(word, spec);
}
}
return spec;
};
parseFunctionReturn = function(fnc, spec) {
var value, ret;
ret = spec;
while (!isEmpty(spec)) {
value = first(spec);
switch (value.type.name) {
case "word!":
spec = next(spec);
if (!isEmpty(spec) && isBlock(first(spec))) {
spec = next(spec);
}
break;
case "string!":
spec = next(spec);
break;
default:
invalidSpecValue(value, spec);
}
}
fnc.type.setReturn(fnc, ret);
return spec;
};
collectLocals = function(fnc) {
return bind(collectSetWords(make(block_type, null), fnc.body, true), fnc.context, false, true);
};
applyGeneric = function(func, args, collectArgs) {
var funcArgs;
funcArgs = func.type.prepareArguments(func);
collectArgs(func, funcArgs, args);
return func.type.call(func, funcArgs);
};
applyOnly = function(func, funcArgs, args) {
var value;
_foreach(func.argsList, function(arg, pos) {
if ((value = args.values[pos])) {
checkArgument(arg, value, null);
return (funcArgs = func.type.setArgument(func, funcArgs, arg, value));
} else {
if (!arg.isOptional) {
return error({
category: "Script",
id: "missing-argument",
message: "Not enough arguments in the argument list during APPLY/ONLY"
});
}
}
});
return null;
};
applyObject = function(func, args, obj) {
var value;
_foreach(func.argsList, function(arg) {
if ((value = obj.map[argumentNameOf(arg)])) {
checkArgument(arg, value, null);
return (args = func.type.setArgument(func, args, arg, value));
} else {
if (!arg.isOptional) {
return error({
category: "Script",
id: "missing-argument",
message: "Missing mandatory argument",
args: arg.word
});
}
}
});
return null;
};
applyContext = function(func, args, ctx) {
var value;
_foreach(func.argsList, function(arg) {
if ((0 <= (value = getWordOffset(ctx, argumentNameOf(arg)))) && (value = ctx.values[value])) {
checkArgument(arg, value, null);
return (args = func.type.setArgument(func, args, arg, value));
} else {
if (!arg.isOptional) {
return error({
category: "Script",
id: "missing-argument",
message: "Missing mandatory argument",
args: arg.word
});
}
}
});
return null;
};
applyBlock = function(func, args, block) {
return parseNamedArguments(func, args, block, "Script", make(logic_type, true), function(block) {
return doStep(first(block), block);
}, checkArgument);
};
native_type = {
type: datatype_type,
name: "native!",
make: function(_arguments) {
var fnc;
fnc = makeFuncObject(native_type, _arguments[0]);
fnc._arguments = [];
fnc.func = _arguments[1];
fnc.compile = _arguments[2];
parseFunctionSpec(fnc);
return fnc;
},
topazMake: function(block) {
var spec, name, f, c;
if (!isBlock(block)) {
error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE NATIVE!",
args: block
});
}
if (2 != lengthOf(block)) {
error({
category: "Script",
id: "invalid-spec",
message: "MAKE NATIVE! requires a spec and a name, not",
args: block
});
}
if (!isBlock((spec = first(block)))) {
error({
category: "Script",
id: "invalid-spec",
message: "Native spec must be a BLOCK!, not",
args: spec.type
});
}
if (!isWord((name = second(block)))) {
error({
category: "Script",
id: "invalid-spec",
message: "Native name must be a WORD!, not",
args: name.type
});
}
name = name.word;
f = natives[nameToJs(name)];
c = nativeCompilers[nameToJs(name)];
return native_type.make([spec, f, c]);
},
mold: function(func, only, flat, limit, indent) {
return limitString("native " + mold(func.spec, false, flat, limit, indent), limit);
},
_do: functionDo,
prepareArguments: function(func) {
return cloneArray(func._arguments);
},
call: function(func, args) {
if (!func.func) {
error({
category: "Internal",
id: "not-implemented",
message: "This is a compiler-only native"
});
}
return func.func.apply(null, args);
},
bind: defaultBind,
compile: function(func, block) {
return functionCompile(func, block, false);
},
callCompile: function(func, args) {
if (!func.compile) {
error({
category: "Internal",
id: "not-implemented",
message: "This is a interpreter-only native"
});
}
return func.compile.apply(null, args);
},
isEqual: isDefaultEqual,
getPath: functionGetPath,
setArgument: function(func, args, arg, value) {
args[arg.offset] = value;
return args;
},
getArgument: function(func, args, arg) {
return args[arg.offset];
},
addArgument: function(func, arg, dflt) {
arg.offset = lengthOfArray(func._arguments);
return func._arguments.push(dflt);
},
setReturn: function(func, spec) {
return null;
},
compileDefaultArguments: function(func, args, isWrap) {
return _foreach(args, function(value, pos) {
if (value) {
return (args[pos] = isWrap ? make(expression_type, astValue(value)) : astValue(value));
}
});
}
};
datatypes.push(native_type);
mezzAction_type = {
type: datatype_type,
name: "mezz-action!",
make: function(_arguments) {
var obj;
obj = {
type: mezzAction_type,
body: _arguments.block,
context: _arguments.context
};
collectLocals(obj);
bind(obj.body, obj.context, false, false);
obj.defaultValues = obj.context.values;
return obj;
},
mold: function(actn, only, flat, limit, indent) {
return mold(actn.body, only, flat, limit, indent);
},
call: function(actn, subAction, args, depth) {
var res, saved;
saved = subAction.context.values;
subAction.context.values = cloneArray(subAction.defaultValues);
_foreach(args, function(value, pos) {
return (subAction.context.values[pos + 1] = value);
});
try {
res = _do(subAction.body);
} catch (e) {
e = handleJsError(e);
if ((e.type.name == "return-value!") && (e.func == actn)) {
res = e.value;
} else {
throw e;
}
}
subAction.context.values = saved;
return res;
}
};
nativeAction_type = {
type: datatype_type,
name: "native-action!",
make: function(_arguments) {
return {
type: nativeAction_type,
name: _arguments.name,
func: _arguments.func,
compile: _arguments.comp
};
},
mold: function(actn, only, flat, limit, indent) {
return limitString("native '" + actn.name, limit);
},
call: function(actn, subAction, args, depth) {
if (!subAction.func) {
error({
category: "Internal",
id: "not-implemented",
message: "This is a compiler-only action"
});
}
return subAction.func.apply(null, args);
}
};
actionAction_type = {
type: datatype_type,
name: "action-action!",
make: function(_arguments) {
return {
type: actionAction_type,
map: {},
list: []
};
},
mold: function(actn, only, flat, limit, indent) {
return moldWordsAndValues("action [", flat ? "]" : (("\n" + indent) + "]"), limit, actn.list, function(word) {
return actn.map[word];
}, flat ? " " : (("\n" + indent) + " "), flat, indent + " ");
},
call: function(actn, subAction, args, depth) {
var subSubAction;
if (!(subSubAction = subAction.map[args[depth].type.name] || subAction.map["default"])) {
error({
category: "Script",
id: "invalid-argument",
message: "Cannot use this action on values of type",
args: args[depth].type
});
}
return subSubAction.type.call(actn, subSubAction, args, depth + 1);
}
};
action_type = {
type: datatype_type,
name: "action!",
make: function(_arguments) {
var fnc;
fnc = makeFuncObject(action_type, _arguments.spec);
fnc.map = {};
fnc.list = [];
fnc.defaultValues = [];
fnc.returnFnc = null;
parseFunctionSpec(fnc);
if (fnc.mandatory < 1) {
error({
category: "Script",
id: "invalid-spec",
message: "ACTION!s require at least one mandatory argument"
});
}
return fnc;
},
topazMake: function(block) {
var actn;
if (!isBlock(block)) {
error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE ACTION!",
args: block
});
}
if (2 != lengthOf(block)) {
error({
category: "Script",
id: "invalid-spec",
message: "MAKE ACTION! requires a spec and a body, not",
args: block
});
}
if (!isBlock(first(block))) {
error({
category: "Script",
id: "invalid-spec",
message: "Function spec must be a BLOCK!"
});
}
if (!isBlock(second(block))) {
error({
category: "Script",
id: "invalid-spec",
message: "Action body must be a BLOCK!"
});
}
actn = action_type.make({
spec: first(block)
});
parseActionBody(actn, actn.map, actn.list, null, second(block), 1);
return actn;
},
mold: function(actn, only, flat, limit, indent) {
var result;
result = "action ";
if (isWithinLimit(result, limit)) {
result = (result + mold(actn.spec, false, flat, subtractLimit(result, limit), indent)) + " [";
}
return moldWordsAndValues(result, flat ? "]" : (("\n" + indent) + "]"), limit, actn.list, function(word) {
return actn.map[word];
}, flat ? " " : (("\n" + indent) + " "), flat, indent + " ");
},
_do: functionDo,
prepareArguments: function(actn) {
return cloneArray(actn.defaultValues);
},
call: function(actn, args) {
var subAction;
if (!(subAction = actn.map[args[0].type.name] || actn.map["default"])) {
error({
category: "Script",
id: "invalid-argument",
message: "Cannot use this action on values of type",
args: args[0].type
});
}
return subAction.type.call(actn, subAction, args, 1);
},
bind: defaultBind,
compile: function(actn, block) {
return error({
category: "Internal",
id: "not-implemented",
message: "Cannot compile ACTION!s yet"
});
},
isEqual: isDefaultEqual,
getPath: functionGetPath,
setArgument: function(actn, args, arg, value) {
args[arg.offset] = value;
return args;
},
getArgument: function(actn, args, arg) {
return args[arg.offset];
},
addArgument: function(actn, arg, dflt) {
arg.offset = lengthOfArray(actn.defaultValues);
return (actn.defaultValues[arg.offset] = dflt);
},
setReturn: function(actn, spec) {
return (actn.returnFnc = make(return_type, {
spec: spec,
func: actn
}));
},
compileDefaultArguments: function(actn, args, isWrap) {
return null;
}
};
datatypes.push(action_type);
parseActionBody = function(actn, actionMap, list, context, body, depth) {
var type, value, actionType, orig, tmp;
if (depth > actn.mandatory) {
error({
category: "Script",
id: "invalid-spec",
message: "Action body spec is too deep (switching on an optional or non-existent argument)"
});
}
while (!isEmpty(body)) {
type = first(body);
if (!isSetWord(type)) {
error({
category: "Script",
id: "invalid-spec",
message: "Invalid action body spec: expected SET-WORD!, not",
args: type.type,
stack: body
});
}
type = type.word;
orig = body;
body = next(body);
if (isEmpty(body)) {
error({
category: "Script",
id: "invalid-spec",
message: "Invalid action body spec: missing body after type name",
stack: orig
});
}
value = first(body);
if (isWord(value) && ((value.word == "native") || (value.word == "action"))) {
actionType = value.word;
body = next(body);
if (isEmpty(body)) {
error({
category: "Script",
id: "invalid-spec",
message: "Invalid action body spec: missing argument",
stack: orig
});
}
var _tmp = doStep(first(body), body);
value = _tmp[0];
body = _tmp[1];
switch (actionType) {
case "native":
if (!isWord(value)) {
error({
category: "Script",
id: "invalid-spec",
message: "Invalid action body spec: NATIVE expected WORD!, not",
args: value.type,
stack: orig
});
}
tmp = nameToJs(value.word);
value = make(nativeAction_type, {
name: value.word,
func: natives[value],
comp: nativeCompilers[value]
});
break;
case "action":
if (!isBlock(value)) {
error({
category: "Script",
id: "invalid-spec",
message: "Invalid action body spec: ACTION expected BLOCK!, not",
args: value.type,
stack: orig
});
}
tmp = make(actionAction_type, null);
context = parseActionBody(actn, tmp.map, tmp.list, context, value, depth + 1);
value = tmp;
break;
}
} else {
var _tmp = doStep(value, body);
value = _tmp[0];
body = _tmp[1];
if (!isBlock(value)) {
error({
category: "Script",
id: "invalid-spec",
message: "Invalid action body spec: expected BLOCK!, not",
args: value.type,
stack: orig
});
}
if (!context) {
context = make(context_type, null);
_foreach(actn.argsList, function(arg) {
return addWord(context, arg.word, actn.defaultValues[arg.offset]);
});
if (actn.returnFnc) {
setInContext(context, make(word_type, "return"), actn.returnFnc);
}
}
value = make(mezzAction_type, {
block: value,
context: context
});
}
if (!actionMap[type]) {
appendArray(list, type);
}
actionMap[type] = value;
}
return context;
};
return_type = {
type: datatype_type,
name: "return!",
make: function(_arguments) {
var fnc;
fnc = makeFuncObject(return_type, _arguments.spec);
fnc.func = _arguments.func;
parseFunctionSpec(fnc);
return fnc;
},
topazMake: function(ignored) {
return error({
category: "Script",
id: "invalid-argument",
message: "Cannot MAKE RETURN!"
});
},
mold: function(func, only, flat, limit, indent) {
return limitString(("#[return " + mold(func.spec, false, flat, limit, indent)) + "]", limit);
},
_do: functionDo,
prepareArguments: function(func) {
if (func.mandatory == 0) {
return make(none_type, null);
} else if (func.mandatory == 1) {
return null;
} else {
return make(object_type, null);
}
},
call: function(func, args) {
throw make(returnValue_type, {
value: args,
func: func.func
});
},
bind: defaultBind,
compile: function(func, block) {
var args;
return error({
category: "Internal",
id: "not-implemented",
message: "Cannot compile RETURN! values"
});
},
isEqual: isDefaultEqual,
getPath: functionGetPath,
setArgument: function(func, args, arg, value) {
if (func.mandatory == 1) {
return value;
} else {
setWordInObject(args, argumentNameOf(arg), value);
return args;
}
},
getArgument: function(func, args, arg) {
if (func.mandatory == 1) {
return args;
} else {
return args.map[argumentNameOf(arg)];
}
},
addArgument: function(func, arg, dflt) {
return null;
}
};
datatypes.push(return_type);
op_type = {
type: datatype_type,
name: "op!",
make: function(_arguments) {
if (2 != _arguments.mandatory) {
error({
category: "Script",
id: "invalid-argument",
message: "Function must take exactly two arguments"
});
}
return {
type: op_type,
func: _arguments
};
},
topazMake: function(func) {
switch (func.type.name) {
case "function!":
return op_type.make(func);
case "native!":
return op_type.make(func);
case "action!":
return op_type.make(func);
default:
return error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE OP!",
args: func
});
}
},
mold: function(op, only, flat, limit, indent) {
return limitString("make op! " + mold(op.func, false, flat, limit, indent), limit);
},
_do: function(op, block) {
return error({
category: "Script",
id: "invalid-infix",
message: "Infix use of OP!s is not supported"
});
},
bind: defaultBind,
compile: function(op, block) {
return error({
category: "Compilation",
id: "invalid-infix",
message: "Infix use of OP!s is not supported"
});
},
isEqual: isDefaultEqual
};
datatypes.push(op_type);
isOperator = function(block) {
var value;
return !isEmpty(block) && isWord((value = first(block))) && (value = get(value, true)) && isOp(value) && value;
};
doOp = function(op, arg1, arg2) {
var args;
args = op.func.type.prepareArguments(op.func);
args = op.func.type.setArgument(op.func, args, op.func.argsList[0], arg1);
args = op.func.type.setArgument(op.func, args, op.func.argsList[1], arg2);
return op.func.type.call(op.func, args);
};
sliceString = function(start, endOrLength) {
var end;
if (isAnyString(endOrLength)) {
end = endOrLength.pos;
} else {
end = start.pos + endOrLength.number;
}
return makeString(start.type, sliceArray(start.string, start.pos, end), 0);
};
string_type = {
type: datatype_type,
name: "string!",
make: function(_arguments) {
return makeString(string_type, _arguments, 0);
},
topazMake: function(value) {
switch (value.type.name) {
case "none!":
return makeString(string_type, "", 0);
case "string!":
return makeString(string_type, toJsString(value), 0);
case "file!":
return makeString(string_type, toJsString(value), 0);
default:
return makeString(string_type, mold(value, false, false, null, ""), 0);
}
},
lengthOf: function(string) {
return lengthOfArray(string.string) - string.pos;
},
pick: function(string, pos) {
return string.string[string.pos + pos];
},
skip: function(string, amount) {
return makeString(string_type, string.string, string.pos + amount);
},
tail: function(string) {
return makeString(string_type, string.string, lengthOfArray(string.string));
},
mold: function(string, only, flat, limit, indent) {
return limitString(("\"" + escape(limitString(string.string, limit))) + "\"", limit);
},
_do: function(string, block) {
return [string, skip(block, 1)];
},
bind: defaultBind,
compile: function(string, block) {
return [astValue(string), skip(block, 1)];
},
isEqual: isDefaultEqual,
copy: function(string) {
return makeString(string_type, toJsString(string), 0);
},
slice: sliceString
};
datatypes.push(string_type);
isEqualString = function(string1, string2) {
return toJsString(string1) == toJsString(string2);
};
string_type["string!"] = {
isEqual: isEqualString
};
string_type["file!"] = {
isEqual: isEqualString
};
escape = function(str) {
var re, result, match, i;
re = /[\x00-\x19^"]/mg;
result = "";
i = 0;
while ((match = re.exec(str))) {
result = result + str.substr(i, match.index - i);
switch (str[match.index]) {
case "^":
result = result + "^^";
break;
case "\n":
result = result + "^/";
break;
case "\"":
result = result + "^\"";
break;
case "\t":
result = result + "^-";
break;
case "\u001e":
result = result + "^!";
break;
default:
result = (result + "^") + String.fromCharCode(str.charCodeAt(match.index) + 64);
}
i = match.index + 1;
}
if (i == 0) {
return str;
} else {
return result + str.substr(i);
}
};
unescape = function(str) {
var result, i, j, ch, p;
result = "";
i = 0;
while (0 <= (j = str.indexOf("^", i))) {
result = result + str.substr(i, j - i);
ch = str[j + 1];
switch (ch) {
case "/":
result = result + "\n";
i = j + 2;
break;
case "-":
result = result + "\t";
i = j + 2;
break;
case "^":
result = result + "^";
i = j + 2;
break;
case "!":
result = result + "\u001e";
i = j + 2;
break;
default:
if ((ch >= "@") && (ch <= "_")) {
result = result + String.fromCharCode(ch.charCodeAt(0) - 64);
i = j + 2;
} else if ((ch == "(") && (p = execRe(str.substr(j + 1), /^\(([0-9A-Fa-f]+)\)/))) {
result = result + String.fromCharCode(parseInt(p[1], 16));
i = (j + 1) + lengthOfArray(p[0]);
} else {
result = result + ch;
i = j + 2;
}
}
}
if (i == 0) {
return str;
} else {
return result + str.substr(i);
}
};
toJsString = function(string) {
return string.string.substr(string.pos);
};
isMatchString = function(string, match) {
return match == string.string.substr(string.pos, match.length);
};
makeString = function(type, string, pos) {
return {
type: type,
string: string,
pos: pos
};
};
parseString = function(text) {
var t;
if ("\"" == pick(text, 0)) {
t = execRe(toJsString(text), /^"(([^"^\f\n\r]*|\^\([0-9A-Fa-f]+\)|\^[^\f\n\r])*)"/);
if (t) {
return [make(string_type, unescape(t[1])), skip(text, lengthOfArray(t[0]))];
} else {
return error({
category: "Syntax",
id: "load-error",
message: "Missing \"",
stack: text
});
}
}
};
char_type = {
type: datatype_type,
name: "char!",
make: function(_arguments) {
return {
type: char_type,
string: _arguments
};
},
topazMake: function(value) {
switch (value.type.name) {
case "number!":
return char_type.make(String.fromCharCode(value.number));
case "char!":
return char_type.make(value.string);
case "none!":
return char_type.make(String.fromCharCode(0));
default:
return error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE CHAR!",
args: value
});
}
},
mold: function(char, only, flat, limit, indent) {
return limitString(("#\"" + escape(char.string)) + "\"", limit);
},
_do: function(char, block) {
return [char, skip(block, 1)];
},
bind: defaultBind,
compile: function(char, block) {
return [astValue(char), skip(block, 1)];
},
isEqual: isDefaultEqual,
copy: function(value) {
return value;
}
};
datatypes.push(char_type);
char_type["char!"] = {
isEqual: function(char1, char2) {
return char1.string == char2.string;
}
};
parseChar = function(text) {
var ch;
ch = execRe(toJsString(text), /^#"(\^?.|\^\([0-9A-Fa-f]+\))"/);
if (ch) {
return [make(char_type, unescape(ch[1])), skip(text, lengthOfArray(ch[0]))];
}
};
file_type = {
type: datatype_type,
name: "file!",
make: function(_arguments) {
return makeString(file_type, _arguments, 0);
},
topazMake: function(value) {
switch (value.type.name) {
case "none!":
return makeString(file_type, "", 0);
case "string!":
return makeString(file_type, toJsString(value), 0);
case "file!":
return makeString(file_type, toJsString(value), 0);
default:
return makeString(file_type, mold(value, false, false, null, ""), 0);
}
},
lengthOf: function(string) {
return lengthOfArray(string.string) - string.pos;
},
pick: function(string, pos) {
return string.string[string.pos + pos];
},
skip: function(string, amount) {
return makeString(file_type, string.string, string.pos + amount);
},
tail: function(string) {
return makeString(file_type, string.string, lengthOfArray(string.string));
},
mold: function(string, only, flat, limit, indent) {
return limitString("%" + escape(limitString(string.string, limit)), limit);
},
_do: function(string, block) {
return [string, skip(block, 1)];
},
bind: defaultBind,
compile: function(string, block) {
return [astValue(string), skip(block, 1)];
},
isEqual: isDefaultEqual,
copy: function(string) {
return makeString(file_type, toJsString(string), 0);
},
slice: sliceString
};
datatypes.push(file_type);
file_type["string!"] = {
isEqual: isEqualString
};
file_type["file!"] = {
isEqual: isEqualString
};
parseFile = function(text) {
var t;
if ("%" == pick(text, 0)) {
t = execRe(toJsString(text), /^%(([^^\f\n\r \])]*|\^\([0-9A-Fa-f]+\)|\^[^\f\n\r])*)/);
if (t) {
return [make(file_type, unescape(t[1])), skip(text, lengthOfArray(t[0]))];
} else {
return error({
category: "Internal",
id: "should-not-happen",
message: "This should not happen (parse-file, no match)",
stack: text
});
}
}
};
none_type = {
type: datatype_type,
name: "none!",
make: function(ignored) {
return noneValue;
},
topazMake: function(ignored) {
return noneValue;
},
mold: function(value, only, flat, limit, indent) {
return limitString("none", limit);
},
_do: function(value, block) {
return [value, skip(block, 1)];
},
bind: defaultBind,
compile: function(value, block) {
return [astValue(value), skip(block, 1)];
},
isEqual: isDefaultEqual,
copy: function(value) {
return value;
}
};
datatypes.push(none_type);
none_type["none!"] = {
isEqual: function(val1, val2) {
return true;
}
};
noneValue = {
type: none_type
};
logic_type = {
type: datatype_type,
name: "logic!",
make: function(value) {
if (value) {
return trueValue;
} else {
return falseValue;
}
},
topazMake: function(value) {
switch (value.type.name) {
case "none!":
return falseValue;
case "logic!":
return value;
default:
return trueValue;
}
},
mold: function(value, only, flat, limit, indent) {
return limitString(value.value ? "true" : "false", limit);
},
_do: function(value, block) {
return [value, skip(block, 1)];
},
bind: defaultBind,
compile: function(value, block) {
return [astValue(value), skip(block, 1)];
},
isEqual: isDefaultEqual
};
datatypes.push(logic_type);
logic_type["logic!"] = {
isEqual: function(logic1, logic2) {
return logic1.value == logic2.value;
}
};
trueValue = {
type: logic_type,
value: true
};
falseValue = {
type: logic_type,
value: false
};
number_type = {
type: datatype_type,
name: "number!",
make: function(_arguments) {
return {
type: number_type,
number: _arguments
};
},
topazMake: function(value) {
switch (value.type.name) {
case "none!":
return number_type.make(0);
case "number!":
return value;
case "string!":
return number_type.make(parseFloat(value.string));
case "char!":
return number_type.make(value.string.charCodeAt(0));
case "logic!":
return number_type.make(value.value + 0);
default:
return error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE NUMBER!",
args: value
});
}
},
mold: function(number, only, flat, limit, indent) {
return limitString(number.number.toString(), limit);
},
_do: function(number, block) {
return [number, skip(block, 1)];
},
bind: defaultBind,
compile: function(number, block) {
return [astValue(number), skip(block, 1)];
},
isEqual: isDefaultEqual
};
datatypes.push(number_type);
number_type["number!"] = {
isEqual: function(num1, num2) {
return num1.number == num2.number;
}
};
parseNumber = function(text) {
var t;
t = execRe(toJsString(text), /^[-+]?[0-9]+(\.[0-9]*)?([Ee][-+]?[0-9]{1,3})?/);
if (t && (0 < lengthOfArray((t = t[0])))) {
return [make(number_type, parseFloat(t)), skip(text, lengthOfArray(t))];
}
};
expression_type = {
type: datatype_type,
name: "expression!",
make: function(_arguments) {
return {
type: expression_type,
expr: _arguments
};
},
topazMake: function(code) {
if (code.type.name == "block!") {
code = compile(code);
if (1 == lengthOfArray(code.list)) {
code = code.list[0];
}
return expression_type.make(code);
} else {
return error({
category: "Script",
id: "invalid-argument",
message: "Invaild argument for MAKE EXPRESSION!",
args: code
});
}
},
mold: function(expr, only, flat, limit, indent) {
var js;
try {
js = toJs(expr.expr, "statement");
} catch (e) {
js = "TO-JS error";
}
return limitString(((("#[expr " + expr.expr.nodeType) + " {") + js) + "}]", limit);
},
_do: function(expr, block) {
return [expr, skip(block, 1)];
},
bind: defaultBind,
compile: function(expr, block) {
return [expr.expr, skip(block, 1)];
},
isEqual: isDefaultEqual,
getPath: function(expr, selector) {
if ((selector.type.name != "word!") || (selector.word != "value") || !testRe(expr.expr.nodeType, /^value\/.*/)) {
error({
category: "Script",
id: "invalid-path",
message: "Invalid path value",
args: selector
});
}
return expr.expr.value;
},
copy: function(value) {
return value;
}
};
datatypes.push(expression_type);
object_type = {
type: datatype_type,
name: "object!",
make: function(args) {
if (!args) {
args = {};
}
return {
type: object_type,
parent: args.parent,
words: args.words || [],
map: args.map || {}
};
},
topazMake: function(code) {
var obj, parent;
switch (code.type.name) {
case "block!":
parent = first(code);
if (parent.type.name == "none!") {
parent = null;
} else if (parent.type.name != "object!") {
error({
category: "Script",
id: "invalid-spec",
message: "Invalid object parent",
args: parent
});
}
obj = make(object_type, {
parent: parent
});
parseObjectSpec(obj, second(code));
break;
case "object!":
obj = make(object_type, {
parent: code
});
break;
default:
error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE OBJECT!",
args: code
});
}
return obj;
},
mold: function(obj, only, flat, limit, indent) {
var result;
if (only) {
result = "[";
} else {
result = "object ";
if (isWithinLimit(result, limit)) {
result = (result + (obj.parent ? mold(obj.parent, false, flat, limit ? (limit - lengthOfArray(result)) : null, indent) : "none")) + " [";
}
}
return moldWordsAndValues(result, flat ? "]" : (("\n" + indent) + "]"), limit, obj.words, function(word) {
return obj.map[word];
}, flat ? " " : (("\n" + indent) + " "), flat, indent + " ");
},
_do: function(object, block) {
return [object, skip(block, 1)];
},
bind: defaultBind,
compile: function(object, block) {
return [astValue(object), skip(block, 1)];
},
getPath: function(object, selector) {
if (!isAnyWord(selector)) {
error({
category: "Script",
id: "invalid-path",
message: "Invalid path value",
args: selector
});
}
return getWordInObject(object, selector.word);
},
setPath: function(object, selector, setTo) {
if (!isAnyWord(selector)) {
error({
category: "Script",
id: "invalid-path",
message: "Invalid path value",
args: selector
});
}
return setWordInObject(object, selector.word, setTo);
},
isEqual: isDefaultEqual,
isIn: function(object, word) {
if (!isAnyWord(word)) {
error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for IN? OBJECT!",
args: word
});
}
return make(logic_type, object.map[word.word] ? true : false);
}
};
datatypes.push(object_type);
isInObject = function(obj, word) {
return 0 <= obj.words.indexOf(word);
};
setWordInObject = function(object, word, setTo) {
if (!isInObject(object, word)) {
appendArray(object.words, word);
}
return (object.map[word] = setTo);
};
getWordInObject = function(object, word) {
return object.map[word] || (object.parent ? getWordInObject(object.parent, word) : make(none_type, null));
};
parseObjectSpec = function(obj, spec) {
var word, value;
while (!isEmpty(spec)) {
word = first(spec);
switch (word.type.name) {
case "set-word!":
var _tmp = doStep(second(spec), next(spec));
value = _tmp[0];
spec = _tmp[1];
setWordInObject(obj, word.word, value);
break;
case "word!":
spec = next(spec);
setWordInObject(obj, word.word, make(logic_type, true));
break;
default:
error({
category: "Script",
id: "invalid-spec",
message: "Invalid object spec value",
args: word,
stack: spec
});
}
}
return obj;
};
exportObjectToContext = function(object, context) {
if (object.parent) {
exportObjectToContext(object.parent, context);
}
_foreach(object.words, function(word) {
return setInContext(context, make(word_type, word), object.map[word]);
});
return null;
};
throw_type = {
type: datatype_type,
name: "throw!",
make: function(_arguments) {
return {
type: throw_type,
value: _arguments
};
},
topazMake: function() {
return shouldNotHappen("MAKE", "THROW!");
},
mold: function() {
return shouldNotHappen("MOLD", "THROW!");
},
_do: function() {
return shouldNotHappen("DO", "THROW!");
},
bind: function() {
return shouldNotHappen("BIND", "THROW!");
},
compile: function() {
return shouldNotHappen("COMPILE", "THROW!");
},
isEqual: isDefaultEqual,
copy: function(value) {
return value;
}
};
shouldNotHappen = function(name, type) {
return error({
category: "Internal",
id: "should-not-happen",
message: ((name + " on ") + type) + " should not happen"
});
};
returnValue_type = {
type: datatype_type,
name: "return-value!",
make: function(_arguments) {
return {
type: returnValue_type,
value: _arguments.value,
func: _arguments.func
};
},
topazMake: function() {
return shouldNotHappen("MAKE", "RETURN!");
},
mold: function() {
return shouldNotHappen("MOLD", "RETURN!");
},
_do: function() {
return shouldNotHappen("DO", "RETURN!");
},
bind: function() {
return shouldNotHappen("BIND", "RETURN!");
},
compile: function() {
return shouldNotHappen("COMPILE", "RETURN!");
},
isEqual: isDefaultEqual,
copy: function(value) {
return value;
}
};
error_type = {
type: datatype_type,
name: "error!",
make: function(args) {
var res;
if (!args) {
args = {};
}
res = {
type: error_type,
category: args.category || "Internal",
id: args.id || "unspecified",
message: args.message || "Unspecified error",
args: args.args,
stack: make(block_type, null)
};
if (args.stack) {
insert(res.stack, args.stack, true, false);
}
return res;
},
topazMake: function(value) {
var blk;
switch (value.type.name) {
case "block!":
blk = make(block_type, null);
append(blk, make(none_type, null));
insert(tail(blk), value, true, false);
return topazMakeError(object_type.topazMake(blk));
case "object!":
return topazMakeError(value);
case "error!":
return value;
default:
return error_type.make({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE ERROR!",
args: value
});
}
},
mold: function(err, only, flat, limit, indent) {
var result;
return limitString(flat ? ((((((((((("make error! [category: '" + err.category) + " id: '") + err.id) + " ") + "message: \"") + escape(err.message)) + "\" ") + (err.args ? (("args: " + mold(isWord(err.args) ? make(litWord_type, err.args.word) : err.args, false, true, limit, "")) + " ") : "")) + "stack: ") + mold(err.stack, false, true, limit, "")) + "]") : ((((((((((((((((((("make error! [\n" + indent) + " category: '") + err.category) + "\n") + indent) + " id: '") + err.id) + "\n") + indent) + " message: \"") + escape(err.message)) + "\"\n") + (err.args ? (((indent + " args: ") + mold(isWord(err.args) ? make(litWord_type, err.args.word) : err.args, false, false, limit, indent + " ")) + "\n") : "")) + indent) + " stack: ") + mold(err.stack, false, false, limit, indent + " ")) + "\n") + indent) + "]"), limit);
},
_do: function(err, block) {
return [err, skip(block, 1)];
},
bind: defaultBind,
compile: function(err, block) {
return [astValue(err), skip(block, 1)];
},
isEqual: isDefaultEqual,
copy: function(value) {
return value;
}
};
datatypes.push(error_type);
error_type["error!"] = {
isEqual: function(err1, err2) {
return (err1.category == err2.category) && (err1.id == err2.id);
}
};
topazMakeError = function(obj) {
var args, err;
obj = obj.map;
args = {};
if (obj.category) {
args.category = (obj.category.type.name == "word!") ? obj.category.word : null;
}
if (obj.id) {
args.id = (obj.id.type.name == "word!") ? obj.id.word : null;
}
if (obj.message) {
args.message = (obj.message.type.name == "string!") ? obj.message.string : null;
}
if (obj.args && !isNone(obj.args)) {
args.args = obj.args;
}
err = error_type.make(args);
if (obj.stack && isBlock(obj.stack)) {
err.stack = obj.stack;
}
return err;
};
error = function(args) {
throw error_type.make(args);
};
formError = function(err) {
var res;
res = ((("*** " + err.category) + " error: ") + err.message) + (err.args ? (((err.category == "Internal") && isString(err.args)) ? (": " + err.args.string) : (": " + mold(err.args, false, true, 80, ""))) : "");
_foreach(err.stack.values, function(item) {
if ((err.category == "Internal") && isString(item)) {
return (res = ((res + "\n*** JS Stack:\n") + item.string) + "\n");
} else {
return (res = (res + "\n*** Stack: ") + mold(item, true, false, 160, " : "));
}
});
return res;
};
typeset_type = {
type: datatype_type,
name: "typeset!",
make: function(args) {
var ts;
if (!args) {
args = {};
}
ts = {
type: typeset_type,
names: args.names || [],
map: args.map || {}
};
if (args.types) {
_foreach(args.types, function(type) {
if (!ts.map[type.name]) {
appendArray(ts.names, type.name);
return (ts.map[type.name] = true);
}
});
}
return ts;
},
topazMake: function(types) {
var tps;
if (!isBlock(types)) {
error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for MAKE TYPESET! (expected BLOCK!)",
args: types
});
}
tps = [];
_foreachBlk(types, function(value, pos) {
if (value.type.name == "word!") {
value = get(value, false);
}
switch (value.type.name) {
case "datatype!":
return appendArray(tps, value);
case "typeset!":
return _foreach(value.names, function(name) {
return appendArray(tps, {
name: name
});
});
default:
return error({
category: "Script",
id: "invalid-spec",
message: "Invalid spec value for TYPESET!",
args: value,
stack: atBlock(types, pos)
});
}
});
return typeset_type.make({
types: tps
});
},
mold: function(ts, only, flat, limit, indent) {
var list;
list = ts.names.join(" ");
return limitString(only ? list : (("make typeset! [" + list) + "]"), limit);
},
_do: function(ts, block) {
return [ts, skip(block, 1)];
},
bind: defaultBind,
compile: function(ts, block) {
return [astValue(ts), skip(block, 1)];
},
isEqual: isDefaultEqual,
copy: function(value) {
return value;
},
isIn: function(ts, value) {
if (!isDatatype(value)) {
error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for IN? TYPESET! (expected DATATYPE!)",
args: value
});
}
return make(logic_type, ts.map[value.name]);
}
};
datatypes.push(typeset_type);
typeset_type["typeset!"] = {
isEqual: function(ts1, ts2) {
return false;
}
};
isInTypeset = function(ts, name) {
return ts.map[name];
};
anyWord_type = make(typeset_type, {
types: [word_type, setWord_type, getWord_type, litWord_type]
});
isAnyWord = function(value) {
return isInTypeset(anyWord_type, value.type.name);
};
anyBlock_type = make(typeset_type, {
types: [path_type, setPath_type, litPath_type, block_type, paren_type]
});
isAnyBlock = function(value) {
return isInTypeset(anyBlock_type, value.type.name);
};
anyString_type = make(typeset_type, {
types: [string_type, file_type]
});
isAnyString = function(value) {
return isInTypeset(anyString_type, value.type.name);
};
wordActive_type = make(typeset_type, {
types: [function_type, native_type, action_type, return_type]
});
isWordActive = function(value) {
return isInTypeset(wordActive_type, value.type.name);
};
compilerWordActive_type = make(typeset_type, {
types: [function_type, native_type, expression_type]
});
isCompilerWordActive = function(value) {
return isInTypeset(compilerWordActive_type, value.type.name);
};
insertAsBlock_type = make(typeset_type, {
types: [block_type, paren_type]
});
isInsertAsBlock = function(value) {
return isInTypeset(insertAsBlock_type, value.type.name);
};
isBlock = function(value) {
return value.type.name == "block!";
};
isDatatype = function(value) {
return value.type.name == "datatype!";
};
isSetWord = function(value) {
return value.type.name == "set-word!";
};
isString = function(value) {
return value.type.name == "string!";
};
isWord = function(value) {
return value.type.name == "word!";
};
isExpression = function(value) {
return value.type.name == "expression!";
};
isNone = function(value) {
return value.type.name == "none!";
};
isObject = function(value) {
return value.type.name == "object!";
};
isContext = function(value) {
return value.type.name == "context!";
};
isError = function(value) {
return value.type.name == "error!";
};
isOp = function(value) {
return value.type.name == "op!";
};
isChar = function(value) {
return value.type.name == "char!";
};
isNumber = function(value) {
return value.type.name == "number!";
};
skipSpaces = function(text) {
var t, isNewline;
t = execRe(toJsString(text), /^(\s|;.*\n)+/);
isNewline = false;
if (t) {
t = t[0];
isNewline = testRe(t, /\n/);
text = skip(text, lengthOfArray(t));
}
return [text, isNewline];
};
parseWordChars = function(text) {
var t;
if ((t = execRe(toJsString(text), /^[!&*+\-.<=>?A-Z^_`a-z|~-ÿ]['!&*+\-.0-9<=>?A-Z^_`a-z|~-ÿ]*/))) {
return t[0];
}
};
parsePathElement = function(text) {
return parseNumber(text) || parseGetWord(text) || parseWord(text) || parseString(text) || parseFile(text) || parseBlock(text) || parseParen(text) || parseChar(text);
};
parseValue = function(text) {
return parseNumber(text) || parseSetWord(text) || parseSetPath(text) || parsePath(text) || parseLitPath(text) || parseWord(text) || parseLitWord(text) || parseGetWord(text) || parseString(text) || parseFile(text) || parseBlock(text) || parseParen(text) || parseChar(text);
};
parseValues = function(values, text) {
var value, isNewline;
var _tmp = skipSpaces(text);
text = _tmp[0];
isNewline = _tmp[1];
while (!isEmpty(text) && ("]" != first(text)) && (")" != first(text))) {
var _tmp = parseValue(text);
value = _tmp[0];
text = _tmp[1];
if (value) {
values = insert(values, value, true, isNewline);
var _tmp = skipSpaces(text);
text = _tmp[0];
isNewline = _tmp[1];
} else {
error({
category: "Syntax",
id: "load-error",
message: "Parse error",
stack: text
});
}
}
setNewLine(values, isNewline);
return [head(values), text];
};
systemWords = make(context_type, null);
loadRaw = function(type, text) {
var values;
var _tmp = parseValues(make(type, null), text);
values = _tmp[0];
text = _tmp[1];
if (!isEmpty(text)) {
error({
category: "Syntax",
id: "load-error",
message: "Parse error",
stack: text
});
}
return values;
};
load = function(text, all) {
var values;
values = loadRaw(block_type, text);
bind(values, systemWords, false, true);
if (!all && (1 == lengthOf(values))) {
return first(values);
} else {
return values;
}
};
_do = function(block) {
var result;
result = make(none_type, null);
while (!isEmpty(block)) {
var _tmp = doStep(first(block), block);
result = _tmp[0];
block = _tmp[1];
}
return result;
};
read = function(filename) {
var stat, dirContents, result;
try {
stat = fs.statSync(filename);
} catch (e) {
if (e instanceof Error) {
error({
category: "Access",
id: "cannot-stat",
message: "Cannot stat file",
args: make(string_type, e.message)
});
} else {
throw e;
}
}
if (stat.isDirectory()) {
dirContents = fs.readdirSync(filename);
result = make(block_type, null);
_foreach(dirContents, function(item) {
return (result = insert(result, make(file_type, item), false, false));
});
return head(result);
} else {
return make(string_type, fs.readFileSync(filename, "utf8"));
}
};
write = function(filename, text) {
return fs.writeFileSync(filename, text, "utf8");
};
compileOp = function(op, expr1, expr2) {
switch (op.func.type.name) {
case "function!":
return astFunctionCall(op.func.name, [expr1, expr2]);
case "native!":
return op.func.compile(expr1, expr2);
}
};
compileStep = function(block) {
var value, expr, op, expr2;
value = first(block);
try {
var _tmp = value.type.compile(value, block);
expr = _tmp[0];
block = _tmp[1];
} catch (e) {
e = handleJsError(e);
if (e.type.name == "error!") {
insert(tail(e.stack), block, true, false);
}
throw e;
}
while ((op = isOperator(block))) {
block = skip(block, 1);
if (isEmpty(block)) {
error({
category: "Compilation",
id: "missing-argument",
message: "Operator missing its second argument",
stack: skip(block, -2)
});
}
value = first(block);
var _tmp = value.type.compile(value, block);
expr2 = _tmp[0];
block = _tmp[1];
expr = compileOp(op, expr, expr2);
}
return [expr, block];
};
compile = function(block) {
var result, expr;
result = [];
while (!isEmpty(block)) {
var _tmp = compileStep(block);
expr = _tmp[0];
block = _tmp[1];
appendArray(result, expr);
}
return astExpressions(result);
};
invalidSetArgument = function(type, stack) {
return error({
category: "Script",
id: "invalid-argument",
message: "Invalid argument for SET, expected ANY-WORD!, not",
args: type,
stack: stack
});
};
setWords = function(words, values) {
var i, w;
i = 0;
while (i < lengthOf(words)) {
if (!isAnyWord((w = pick(words, i)))) {
invalidSetArgument(w.type, atBlock(words, i));
}
setWord(w, pick(values, i));
i = i + 1;
}
return values;
};
find = function(series, value) {
if (isAnyString(series)) {
if (isAnyString(value)) {
value = toJsString(value);
} else if (isChar(value)) {
value = value.string;
} else {
value = mold(value, false, false, null, "");
}
while (!isEmpty(series) && !isMatchString(series, value)) {
series = skip(series, 1);
}
return series;
} else {
while (!isEmpty(series) && !isEqual(value, first(series))) {
series = skip(series, 1);
}
return series;
}
};
natives = {
make: function(type, spec) {
return type.topazMake(spec);
},
foreach: function(words, series, body) {
var ctx, res;
ctx = make(context_type, null);
switch (words.type.name) {
case "word!":
words = append(make(block_type, null), words);
break;
case "block!":
break;
default:
error({
category: "Script",
id: "invalid-argument",
message: "FOREACH expected a word! or block!, not",
args: words.type
});
}
bind(words, ctx, false, true);
bind(body, ctx, false, false);
res = make(none_type, null);
while (!isEmpty(series)) {
setWords(words, series);
res = _do(body);
series = skip(series, lengthOf(words));
}
return res;
},
set: function(word, value) {
var offset;
if (isAnyWord(word)) {
return setWord(word, value);
} else if (isBlock(word) && isBlock(value)) {
return setWords(word, value);
} else if (isBlock(word) && isObject(value)) {
_foreachBlk(word, function(wrd, pos) {
if (!isAnyWord(wrd)) {
invalidSetArgument(wrd.type, atBlock(word, pos));
}
return setWord(wrd, getWordInObject(value, wrd.word));
});
return value;
} else if (isBlock(word) && isContext(value)) {
_foreachBlk(word, function(wrd, pos) {
if (!isAnyWord(wrd)) {
invalidSetArgument(wrd.type, atBlock(word, pos));
}
if (0 > (offset = getWordOffset(value, wrd.word))) {
error({
category: "Script",
id: "invalid-argument",
message: "The provided context does not have such word",
args: wrd
});
}
return setWord(wrd, value.values[offset]);
});
return value;
} else if (isContext(word) && isObject(value)) {
exportObjectToContext(value, word);
return value;
} else {
return error({
category: "Script",
id: "invalid-argument",
message: "Invalid combination of arguments for SET"
});
}
},
reduce: function(block) {
var result, value;
result = make(block_type, null);
while (!isEmpty(block)) {
var _tmp = doStep(first(block), block);
value = _tmp[0];
block = _tmp[1];
insert(tail(result), value, true, false);
}
return result;
},
insert: function(series, value, only, newLine) {
return series.type.insert(series, value, only.value, newLine.value);
},
head: head,
pick: function(series, index) {
return pick(series, index.number);
},
lengthOf: function(series) {
return make(number_type, lengthOf(series));
},
skip: function(series, amount) {
return skip(series, amount.number);
},
mold: function(value, only, flat, limit, indent) {
return make(string_type, value.type.mold(value, only.value, flat.value, isNone(limit) ? null : limit.number, toJsString(indent)));
},
doBlock: _do,
get: function(word, nya) {
var value;
value = get(word, nya.value);
if (!value) {
value = make(none_type, null);
}
return value;
},
bind: function(words, context, cpy, _new) {
return words.type.bind(words, context, cpy.value, _new.value);
},
tail: tail,
print: function(string) {
print(toJsString(string));
return string;
},
prin: function(string) {
prin(toJsString(string));
return string;
},
loadString: load,
read: function(fileName) {
return read(toJsString(fileName));
},
_try: function(code, word, def) {
var res, ctx;
try {
res = _do(code);
} catch (e) {
e = handleJsError(e);
if (isError(e)) {
ctx = make(context_type, null);
setInContext(ctx, word, e);
res = _do(bind(def, ctx, false, false));
} else {
throw e;
}
}
return res;
},
_catch: function(code) {
var res;
try {
res = _do(code);
} catch (e) {
e = handleJsError(e);
if (e.type.name == "throw!") {
res = e.value;
} else {
throw e;
}
}
return res;
},
formError: function(err) {
return make(string_type, formError(err));
},
add: function(val1, val2) {
return make(number_type, val1.number + val2.number);
},
subtract: function(val1, val2) {
return make(number_type, val1.number - val2.number);
},
isEqual: function(val1, val2) {
return make(logic_type, isEqual(val1, val2));
},
isGreater: function(val1, val2) {
return make(logic_type, val1.number > val2.number);
},
isLesser: function(val1, val2) {
return make(logic_type, val1.number < val2.number);
},
isNotEqual: function(val1, val2) {
return make(logic_type, !isEqual(val1, val2));
},
isGreaterOrEqual: function(val1, val2) {
return make(logic_type, val1.number >= val2.number);
},
isLesserOrEqual: function(val1, val2) {
return make(logic_type, val1.number <= val2.number);
},
compile: function(block) {
return make(string_type, toJs(compile(block), "statement"));
},
write: function(fileName, text) {
write(toJsString(fileName), toJsString(text));
return text;
},
_throw: function(value) {
throw make(throw_type, value);
},
cause: function(err) {
throw err;
},
_if: function(condition, body) {
if (isTrue(condition)) {
return _do(body);
} else {
return make(none_type, null);
}
},
either: function(condition, trueBody, falseBody) {
return _do(isTrue(condition) ? trueBody : falseBody);
},
not: function(value) {
return make(logic_type, !isTrue(value));
},
apply: function(func, args, only) {
if (only.value) {
if (!isBlock(args)) {
error({
category: "Script",
id: "invalid-argument",
message: "Invalid ARGS argument for APPLY/ONLY, expected BLOCK!, not",
args: args.type
});
}
return applyGeneric(func, args, applyOnly);
} else {
switch (args.type.name) {
case "object!":
return applyGeneric(func, args, applyObject);
case "context!":
return applyGeneric(func, args, applyContext);
case "block!":
return applyGeneric(func, args, applyBlock);
}
}
},
_while: function(condition, body, useAny) {
var res, f;
res = make(none_type, null);
f = useAny.value ? natives.any : natives.all;
while (isTrue(f(condition))) {
res = _do(body);
}
return res;
},
until: function(body) {
var res;
res = _do(body);
while (!isTrue(res)) {
res = _do(body);
}
return res;
},
all: function(block) {
var res;
if (isEmpty(block)) {
return make(logic_type, true);
} else {
var _tmp = doStep(first(block), block);
res = _tmp[0];
block = _tmp[1];
while (isTrue(res) && !isEmpty(block)) {
var _tmp = doStep(first(block), block);
res = _tmp[0];
block = _tmp[1];
}
return res;
}
},
any: function(block) {
var res;
if (isEmpty(block)) {
return make(none_type, null);
} else {
var _tmp = doStep(first(block), block);
res = _tmp[0];
block = _tmp[1];
while (!isTrue(res) && !isEmpty(block)) {
var _tmp = doStep(first(block), block);
res = _tmp[0];
block = _tmp[1];
}
return res;
}
},
switchDefault: function(value, cases, def, all) {
var caseBlock, origCases, res;
if (isEmpty((cases = find(cases, value)))) {
return _do(def);
} else {
origCases = cases;
while (!isEmpty(cases) && (caseBlock = first(cases)) && !isBlock(caseBlock)) {
cases = skip(cases, 1);
}
if (isEmpty(cases)) {
error({
category: "Script",
id: "invalid-spec",
message: "SWITCH is missing the case block after the matched value",
stack: origCases
});
} else {
res = _do(caseBlock);
}
if (isTrue(all)) {
return natives.switchDefault(value, next(cases), def, all);
} else {
return res;
}
}
},
find: find,
_case: function(cases, all) {
var cond, res, origCases, block;
if (isEmpty(cases)) {
return make(none_type, null);
} else {
cond = make(none_type, null);
while (!isEmpty(cases) && !isTrue(cond)) {
origCases = cases;
var _tmp = doStep(first(cases), cases);
cond = _tmp[0];
cases = _tmp[1];
if (!isBlock((block = first(cases)))) {
error({
category: "Script",
id: "invalid-spec",
message: "CASE expected a BLOCK! after the condition, not",
args: block.type,
stack: origCases
});
}
cases = skip(cases, 1);
}
if (isTrue(cond)) {
res = _do(block);
if (isTrue(all) && !isEmpty(cases)) {
print("CASE recursing...");
return natives._case(cases, all);
} else {
return res;
}
} else {
return make(none_type, null);
}
}
},
contextOf: function(word) {
return word.context;
},
specOf: function(f) {
return f.spec;
},
bodyOf: function(f) {
return f.body;
},
rejoin: function(block) {
var result, value;
result = "";
while (!isEmpty(block)) {
var _tmp = doStep(first(block), block);
value = _tmp[0];
block = _tmp[1];
value = isAnyString(value) ? toJsString(value) : mold(value, false, false, null, "");
result = result + value;
}
return make(string_type, result);
},
typeOf: function(value) {
return value.type;
},
copy: function(value) {
return value.type.copy(value);
},
isIn: function(container, value) {
return container.type.isIn(container, value);
},
conjure: function(name) {
return values[nameToJs(name.word)];
},
quit: function(exitCode) {
return process.exit(exitCode.number);
},
slice: function(start, endOrLength) {
if (!isNumber(endOrLength) && (endOrLength.type.name != start.type.name)) {
error({
category: "Script",
id: "invalid-argument",
message: "SLICE expected END-OR-LENGTH to be a number or the same series as START"
});
}
return start.type.slice(start, endOrLength);
},
clear: function(series) {
return series.type.clear(series);
},
getCompilerValue: getCompilerValue,
setCompilerValue: setCompilerValue
};
setWord(load(make(string_type, "conjure"), false), make(native_type, [load(make(string_type, "[name]"), false), natives.conjure]));
values = {
make: make(native_type, [load(make(string_type, "[type arguments]"), false), natives.make]),
native_type: native_type,
word_type: word_type,
datatypes: make(block_type, {
values: datatypes,
newlines: makeNewlines(datatypes)
})
};
toJs = function(node, context) {
return node.toJs(node, context);
};
toJsCommon = function(jsText, context) {
switch (context) {
case "return":
return ("return " + jsText) + ";";
case "statement":
return jsText + ";";
default:
return jsText;
}
};
eitherExprToJs = function(node) {
var res;
res = ((toJs(node.condition, "paren") + " ? ") + toJs(node.trueBody, "paren")) + " : ";
if (node.falseBody) {
return res + toJs(node.falseBody, "paren");
} else {
return res + "null";
}
};
eitherStmtToJs = function(node, context) {
var res;
res = ((("if(" + toJs(node.condition, "expression")) + "){") + toJs(node.trueBody, context)) + "}";
if (node.falseBody) {
if (node.falseBody.nodeType == "either") {
res = (res + "else ") + toJs(node.falseBody, context);
} else {
res = ((res + "else{") + toJs(node.falseBody, context)) + "}";
}
}
return res;
};
tryToJs = function(code, name, def, context) {
return ((((("try{" + toJs(code, context)) + "}catch(") + nameToJs(name)) + "){") + toJs(def, context)) + "}";
};
wordsToJs = function(words) {
var res, word;
res = [];
while (!isEmpty(words)) {
word = first(words);
switch (word.type.name) {
case "word!":
appendArray(res, nameToJs(word.word));
break;
case "set-word!":
if (word.word != "options") {
appendArray(res, nameToJs(word.word));
words = next(words);
}
break;
}
words = next(words);
}
return res.join();
};
astValue = function(value) {
return {
nodeType: "value/" + value.type.name,
value: value,
toJs: function(node, context) {
var res;
switch (node.value.type.name) {
case "string!":
res = JSON.stringify(node.value.string);
break;
case "char!":
res = JSON.stringify(node.value.string);
break;
case "number!":
res = node.value.number.toString();
break;
case "none!":
res = "null";
break;
case "logic!":
if (node.value.value) {
res = "true";
} else {
res = "false";
}
break;
default:
error({
category: "JS emitter",
id: "invalid-value",
message: "Cannot represent values of this type in JS",
args: node.value.type
});
}
if (context == "return") {
return ("return " + res) + ";";
} else {
return res;
}
}
};
};
astParen = function(expressions) {
return {
nodeType: "paren",
expressions: expressions,
toJs: function(node, context) {
var res;
return toJs(node.expressions, context);
}
};
};
astLiteral = function(text) {
return {
nodeType: "literal",
text: text,
toJs: function(node, context) {
return toJsCommon(node.text, context);
}
};
};
astGet = function(word) {
return {
nodeType: "get",
word: word,
toJs: function(node, context) {
return toJsCommon(nameToJs(node.word.word), context);
}
};
};
astGetPath = function(expr, selector) {
switch (selector.type.name) {
case "paren!":
selector = compile(selector);
break;
case "get-word!":
selector = astGet(selector);
break;
default:
selector = astValue(selector);
}
return {
nodeType: "get-path",
expr: expr,
selector: selector,
toJs: function(node, context) {
var sel;
if (node.selector.nodeType == "value/word!") {
sel = "." + nameToJs(node.selector.value.word);
} else {
sel = ("[" + toJs(node.selector, "expression")) + "]";
}
return toJsCommon(toJs(node.expr, "expression") + sel, context);
}
};
};
astSetPath = function(expr, selector, setTo) {
switch (selector.type.name) {
case "paren!":
selector = compile(selector);
break;
case "get-word!":
selector = astGet(selector);
break;
default:
selector = astValue(selector);
}
return {
nodeType: "set-path",
expr: expr,
selector: selector,
setTo: setTo,
toJs: function(node, context) {
var res, sel;
if (node.selector.nodeType == "value/word!") {
sel = "." + nameToJs(node.selector.value.word);
} else {
sel = ("[" + toJs(node.selector, "expression")) + "]";
}
res = ((toJs(node.expr, "expression") + sel) + "=") + toJs(node.setTo, "expression");
switch (context) {
case "return":
return ("return (" + res) + ");";
case "statement":
return res + ";";
case "expression":
return ("(" + res) + ")";
case "paren":
return ("(" + res) + ")";
}
}
};
};
astSet = function(word, setTo) {
return {
nodeType: "set",
word: word,
setTo: setTo,
toJs: function(node, context) {
var res, words, name, i;
if (node.word.type.name == "block!") {
if (context != "statement") {
error({
category: "JS emitter",
id: "statement-only",
message: "SET with multiple words can only be a statement"
});
}
words = node.word;
res = ("var _tmp=" + toJs(node.setTo, "expression")) + ";";
i = 0;
while (i < lengthOf(words)) {
name = pick(words, i);
res = (((res + nameToJs(name.word)) + "=_tmp[") + i) + "];";
i = i + 1;
}
return res;
} else {
res = (nameToJs(node.word.word) + "=") + toJs(node.setTo, "expression");
switch (context) {
case "return":
return ("return (" + res) + ");";
case "statement":
return res + ";";
case "expression":
return ("(" + res) + ")";
case "paren":
return ("(" + res) + ")";
}
}
}
};
};
astFunctionCall = function(func, args) {
return {
nodeType: "function-call",
func: func,
args: args,
toJs: function(node, context) {
var res;
res = [];
_foreach(node.args, function(arg) {
return appendArray(res, toJs(arg, "expression"));
});
return toJsCommon(((toJs(node.func, "expression") + "(") + res.join()) + ")", context);
}
};
};
astExpressions = function(list) {
return {
nodeType: "expressions",
list: list,
toJs: function(node, context) {
var i, res;
i = 0;
res = "";
switch (context) {
case "statement":
while (i < lengthOfArray(node.list)) {
res = res + toJs(node.list[i], "statement");
i = i + 1;
}
break;
case "expression":
if (1 < lengthOfArray(node.list)) {
error({
category: "JS emitter",
id: "multiple-expressions",
message: "Multiple expressions in expression context (eg. paren! with more than one expression)"
});
}
res = toJs(node.list[0], "expression");
break;
case "paren":
if (1 < lengthOfArray(node.list)) {
error({
category: "JS emitter",
id: "multiple-expressions",
message: "Multiple expressions in expression context (eg. paren! with more than one expression)"
});
}
res = toJs(node.list[0], "paren");
break;
case "return":
while ((i + 1) < lengthOfArray(node.list)) {
res = res + toJs(node.list[i], "statement");
i = i + 1;
}
if (i < lengthOfArray(node.list)) {
res = res + toJs(node.list[i], "return");
} else {
res = "return null;";
}
break;
}
return res;
}
};
};
astFunctionDefinition = function(spec, locals, body) {
return {
nodeType: "function-definition",
spec: spec,
locals: locals,
body: body,
toJs: function(node, context) {
var res;
res = ("function(" + wordsToJs(node.spec)) + "){";
if (!isEmpty(node.locals)) {
res = ((res + "var ") + wordsToJs(node.locals)) + ";";
}
res = (res + toJs(node.body, "return")) + "}";
switch (context) {
case "return":
return ("return " + res) + ";";
case "expression":
return res;
case "paren":
return ("(" + res) + ")";
case "statement":
return error({
category: "JS emitter",
id: "no-statement",
message: "Function definition cannot be used as a statement"
});
}
}
};
};
astThrow = function(expr) {
return {
nodeType: "throw",
expr: expr,
toJs: function(node, context) {
var res;
res = ("throw " + toJs(node.expr, "expression")) + ";";
switch (context) {
case "expression":
return error({
category: "JS emitter",
id: "statement-only",
message: "THROW cannot be used as an expression"
});
case "statement":
return res;
case "paren":
return error({
category: "JS emitter",
id: "statement-only",
message: "THROW cannot be used as an expression"
});
case "return":
return res;
}
}
};
};
astEither = function(condition, trueBody, falseBody) {
return {
nodeType: "either",
condition: condition,
trueBody: trueBody,
falseBody: falseBody,
toJs: function(node, context) {
switch (context) {
case "statement":
return eitherStmtToJs(node, "statement");
case "expression":
return eitherExprToJs(node);
case "paren":
return ("(" + eitherExprToJs(node)) + ")";
case "return":
return eitherStmtToJs(node, "return");
}
}
};
};
astNot = function(expr) {
return {
nodeType: "not",
expr: expr,
toJs: function(node, context) {
return toJsCommon("!" + toJs(node.expr, "paren"), context);
}
};
};
astStruct = function(spec) {
return {
nodeType: "struct",
spec: spec,
toJs: function(node, context) {
var res, i, name, expr;
res = "{";
if (0 < lengthOfArray(node.spec)) {
name = node.spec[0];
expr = node.spec[1];
res = ((res + nameToJs(name)) + ":") + toJs(expr, "expression");
i = 2;
while (i < lengthOfArray(node.spec)) {
name = node.spec[i];
expr = node.spec[i + 1];
res = (((res + ",") + nameToJs(name)) + ":") + toJs(expr, "expression");
i = i + 2;
}
}
return toJsCommon(res + "}", context);
}
};
};
astReduce = function(exprs) {
return {
nodeType: "reduce",
exprs: exprs,
toJs: function(node, context) {
var res, i, expr;
res = "[";
if (0 < lengthOfArray(node.exprs)) {
expr = node.exprs[0];
res = res + toJs(expr, "expression");
i = 1;
while (i < lengthOfArray(node.exprs)) {
expr = node.exprs[i];
res = (res + ",") + toJs(expr, "expression");
i = i + 1;
}
}
return toJsCommon(res + "]", context);
}
};
};
astWhile = function(condition, body) {
return {
nodeType: "while",
condition: condition,
body: body,
toJs: function(node, context) {
if (context == "statement") {
return ((("while(" + toJs(node.condition, "expression")) + "){") + toJs(node.body, "statement")) + "}";
} else {
return error({
category: "JS emitter",
id: "statement-only",
message: "WHILE can only be used as a statement"
});
}
}
};
};
astPoke = function(arr, pos, value) {
return {
nodeType: "poke",
arr: arr,
pos: pos,
value: value,
toJs: function(node, context) {
var res;
res = (((toJs(node.arr, "paren") + "[") + toJs(node.pos, "expression")) + "]=") + toJs(node.value, "expression");
switch (context) {
case "return":
return ("return (" + res) + ");";
case "statement":
return res + ";";
case "expression":
return ("(" + res) + ")";
case "paren":
return ("(" + res) + ")";
}
}
};
};
astPick = function(arr, pos) {
return {
nodeType: "pick",
arr: arr,
pos: pos,
toJs: function(node, context) {
return toJsCommon(((toJs(node.arr, "paren") + "[") + toJs(node.pos, "expression")) + "]", context);
}
};
};
astAll = function(exprs) {
return {
nodeType: "all",
exprs: exprs,
toJs: function(node, context) {
var res, i;
res = toJs(node.exprs[0], "paren");
i = 1;
while (i < lengthOfArray(node.exprs)) {
res = (res + "&&") + toJs(node.exprs[i], "paren");
i = i + 1;
}
switch (context) {
case "statement":
return error({
category: "JS emitter",
id: "no-statement",
message: "ALL cannot be used as a statement"
});
case "expression":
return res;
case "paren":
return ("(" + res) + ")";
case "return":
return ("return " + res) + ";";
}
}
};
};
astAny = function(exprs) {
return {
nodeType: "any",
exprs: exprs,
toJs: function(node, context) {
var res, i;
res = toJs(node.exprs[0], "paren");
i = 1;
while (i < lengthOfArray(node.exprs)) {
res = (res + "||") + toJs(node.exprs[i], "paren");
i = i + 1;
}
switch (context) {
case "statement":
return error({
category: "JS emitter",
id: "no-statement",
message: "ANY cannot be used as a statement"
});
case "expression":
return res;
case "paren":
return ("(" + res) + ")";
case "return":
return ("return " + res) + ";";
}
}
};
};
astRegexp = function(exp, flags) {
return {
nodeType: "regexp",
exp: exp,
flags: flags,
toJs: function(node, context) {
return toJsCommon((("/" + toJsString(node.exp)) + "/") + toJsString(node.flags), context);
}
};
};
astSwitch = function(value, cases, def) {
return {
nodeType: "switch",
value: value,
cases: cases,
def: def,
toJs: function(node, context) {
var res, i, aCase;
switch (context) {
case "statement":
res = ("switch(" + toJs(node.value, "expression")) + "){";
i = 0;
while (i < lengthOfArray(node.cases)) {
aCase = node.cases[i];
res = ((((res + "case ") + toJs(aCase.value, "expression")) + ":") + toJs(aCase.expr, "statement")) + "break;";
i = i + 1;
}
if (node.def) {
return ((res + "default:") + toJs(node.def, "statement")) + "}";
} else {
return res + "}";
}
case "expression":
return error({
category: "JS emitter",
id: "statement-only",
message: "SWITCH cannot be used as an expression"
});
case "paren":
return error({
category: "JS emitter",
id: "statement-only",
message: "SWITCH cannot be used as an expression"
});
case "return":
res = ("switch(" + toJs(node.value, "expression")) + "){";
i = 0;
while (i < lengthOfArray(node.cases)) {
aCase = node.cases[i];
res = (((res + "case ") + toJs(aCase.value, "expression")) + ":") + toJs(aCase.expr, "return");
i = i + 1;
}
if (node.def) {
return ((res + "default:") + toJs(node.def, "return")) + "}";
} else {
return res + "}";
}
}
}
};
};
astToChar = function(expr) {
return {
nodeType: "to-char",
expr: expr,
toJs: function(node, context) {
return toJsCommon(("String.fromCharCode(" + toJs(node.expr, "expression")) + ")", context);
}
};
};
astTry = function(code, word, def) {
return {
nodeType: "try",
code: code,
word: word,
def: def,
toJs: function(node, context) {
switch (context) {
case "statement":
return tryToJs(node.code, node.word.word, node.def, "statement");
case "expression":
return error({
category: "JS emitter",
id: "statement-only",
message: "TRY cannot be used as an expression"
});
case "paren":
return error({
category: "JS emitter",
id: "statement-only",
message: "TRY cannot be used as an expression"
});
case "return":
return tryToJs(node.code, node.word.word, node.def, "return");
}
}
};
};
astOp = function(op, val1, val2) {
return {
nodeType: "op/" + op,
op: op,
val1: val1,
val2: val2,
toJs: function(node, context) {
var res;
res = (toJs(node.val1, "paren") + node.op) + toJs(node.val2, "paren");
switch (context) {
case "statement":
return res;
case "expression":
return res;
case "paren":
return ("(" + res) + ")";
case "return":
return ("return " + res) + ";";
}
}
};
};
parseStructSpec = function(block) {
var result, name, expr;
block = block.value;
result = [];
while (!isEmpty(block)) {
name = first(block);
if (name.type.name != "set-word!") {
error({
category: "Compilation",
id: "invalid-spec",
message: "Invalid struct spec, expected set-word!, not",
args: name.type,
stack: block
});
}
block = skip(block, 1);
if (isEmpty(block)) {
error({
category: "Compilation",
id: "invalid-spec",
message: "Struct field is missing its value",
stack: skip(block, -1)
});
}
var _tmp = compileStep(block);
expr = _tmp[0];
block = _tmp[1];
appendArray(result, name.word);
appendArray(result, expr);
}
return result;
};
compileReduce = function(block) {
var result, expr;
result = [];
while (!isEmpty(block)) {
var _tmp = compileStep(block);
expr = _tmp[0];
block = _tmp[1];
appendArray(result, expr);
}
return result;
};
compileCases = function(cases) {
var cond, origCases;
if (isEmpty(cases)) {
return null;
} else {
origCases = cases;
var _tmp = compileStep(cases);
cond = _tmp[0];
cases = _tmp[1];
if (isEmpty(cases)) {
error({
category: "Compilation",
id: "invalid-spec",
message: "Missing case block after condition",
stack: origCases
});
}
if (cond.nodeType == "value/word!") {
return compile(first(cases));
} else {
return astEither(cond, compile(first(cases)), compileCases(skip(cases, 1)));
}
}
};
compileSwitch = function(value, cases, def) {
var expr, compiledCases;
cases = cases.value;
compiledCases = [];
while (!isEmpty(cases)) {
var _tmp = compileStep(cases);
expr = _tmp[0];
cases = _tmp[1];
appendArray(compiledCases, {
value: expr,
expr: compile(first(cases))
});
cases = skip(cases, 1);
}
return astSwitch(value, compiledCases, def ? compile(def.value) : null);
};
nativeCompilers = {
add: function(val1, val2) {
return astOp("+", val1, val2);
},
subtract: function(val1, val2) {
return astOp("-", val1, val2);
},
isEqual: function(val1, val2) {
return astOp("==", val1, val2);
},
isGreater: function(val1, val2) {
return astOp(">", val1, val2);
},
isLesser: function(val1, val2) {
return astOp("<", val1, val2);
},
isNotEqual: function(val1, val2) {
return astOp("!=", val1, val2);
},
isGreaterOrEqual: function(val1, val2) {
return astOp(">=", val1, val2);
},
isLesserOrEqual: function(val1, val2) {
return astOp("<=", val1, val2);
},
_function: function(spec, locals, body) {
return astFunctionDefinition(spec.value, locals.value, compile(body.value));
},
none: function() {
return astValue(make(none_type, null));
},
_true: function() {
return astValue(make(logic_type, true));
},
_false: function() {
return astValue(make(logic_type, false));
},
_throw: function(value) {
return astThrow(value);
},
set: function(word, expr) {
return astSet(word.value, expr);
},
_if: function(condition, body) {
return astEither(condition, compile(body.value), null);
},
either: function(condition, trueBody, falseBody) {
return astEither(condition, compile(trueBody.value), compile(falseBody.value));
},
not: function(value) {
return astNot(value);
},
makeStruct: function(spec) {
return astStruct(parseStructSpec(spec));
},
apply: function(func, args, only) {
return astFunctionCall(func, compileReduce(args.value));
},
reduce: function(block) {
return astReduce(compileReduce(block.value));
},
_while: function(condblock, body) {
var condition;
condblock = condblock.value;
if (isEmpty(condblock)) {
error({
category: "Compilation",
id: "invalid-argument",
message: "WHILE's condition block cannot be empty"
});
}
var _tmp = compileStep(condblock);
condition = _tmp[0];
condblock = _tmp[1];
if (!isEmpty(condblock)) {
error({
category: "Compilation",
id: "invalid-argument",
message: "WHILE's condition block can only have one expression",
stack: head(condblock)
});
}
return astWhile(condition, compile(body.value));
},
makeArray: function() {
return astReduce([]);
},
pokeArray: function(arr, pos, value) {
return astPoke(arr, pos, value);
},
pickArray: function(arr, pos) {
return astPick(arr, pos);
},
all: function(block) {
return astAll(compileReduce(block.value));
},
any: function(block) {
return astAny(compileReduce(block.value));
},
regexp: function(exp, flags) {
return astRegexp(exp.value, flags.value);
},
switchDefault: function(value, cases, def) {
return compileSwitch(value, cases, def);
},
_switch: function(value, cases) {
return compileSwitch(value, cases, null);
},
_case: function(cases) {
var expr;
if (isEmpty(cases.value)) {
error({
category: "Compilation",
id: "invalid-spec",
message: "CASE needs at least one case"
});
}
return compileCases(cases.value);
},
toChar: function(number) {
return astToChar(number);
},
_try: function(code, word, def) {
return astTry(compile(code.value), word.value, compile(def.value));
},
rejoin: function(block) {
var expr, res;
block = block.value;
if (isEmpty(block)) {
return astValue(make(string_type, ""));
} else {
var _tmp = compileStep(block);
res = _tmp[0];
block = _tmp[1];
while (!isEmpty(block)) {
var _tmp = compileStep(block);
expr = _tmp[0];
block = _tmp[1];
res = astOp("+", res, expr);
}
return res;
}
},
isJsError: function(value) {
return astOp(" instanceof ", value, astLiteral("Error"));
}
};
sys = require("util");
fs = require("fs");
try {
_do(load(make(string_type, "make: conjure 'make\nnative!: conjure 'native!\nword!: conjure 'word!\ndatatypes: conjure 'datatypes\nforeach: make native! [[words series body] foreach]\nset: make native! [[word value] set]\nreduce: make native! [[block] reduce]\nforeach 'type datatypes [\n set make word! type type\n]\nnone: make none! 0\ntrue: on: yes: make logic! 1\nfalse: off: no: make logic! none\nany-word!: make typeset! [word! get-word! set-word! lit-word!]\nany-path!: make typeset! [path! set-path! lit-path!]\nany-block!: make typeset! [any-path! block! paren!]\nany-function!: make typeset! [function! native! action! return!]\nany-string!: make typeset! [string! file!]\nseries!: make typeset! [any-block! any-string!]\nforeach [native spec] [\n conjure [\n \"Conjure a value\"\n name [word!]\n return: value\n ]\n make [\n \"Make a Topaz type according to spec\"\n type [datatype!]\n spec\n return: value \"A value of the specified type\"\n ]\n foreach [\n \"Repeat the body over all values in the series\"\n words [word! block!] \"Word or words to set to each value\"\n series [series!]\n body [block!]\n return: result \"The result of evaluating the body, or NONE if it is never evaluated\"\n ]\n set [\n \"Set a word to a value or a list of words to a list of values\"\n word [any-word! block! object! context!]\n value\n return: value\n ]\n reduce [\n \"Evaluate and collect all values in the block\"\n block [block!]\n return: reduced-block [block!]\n ]\n insert [\n \"Insert a value at the current position of the series\"\n series [series!]\n value\n options:\n only: no [logic!] \"Insert series as a single value\"\n new-line: no [logic!] \"Temporary - add new line before value on MOLD\"\n return: series\n ]\n head [\n \"Return the series at the head position\"\n series [series!]\n return: series\n ]\n pick [\n \"Pick value in a series\"\n series [series!]\n index [number!]\n return: value\n ]\n length-of [\n \"Return the length of a series\"\n series [series!]\n return: length [number!]\n ]\n skip [\n \"Return the series at a new position\"\n series [series!]\n amount [number!] \"Skip the specified number of values\"\n return: series\n ]\n mold [\n \"Return a LOAD-able text representation of a value\"\n value\n options:\n only: no [logic!] \"Don't generate outer [ ] for block! values\"\n flat: no [logic!] \"Produce a single text line\"\n limit [number! none!] \"Don't return a string longer than LIMIT characters\"\n indent: \"\" [string!] \"Add this string after each new line (ignored if flat)\"\n return: molded-value [string!]\n ]\n do-block [\n \"Evaluate a block\"\n block [block! paren!]\n return: result\n ]\n get [\n \"Return the value of a word in its context\"\n word [any-word!]\n options:\n any: no [logic!] \"If value is not set, return NONE instead of causing an error\"\n return: value\n ]\n bind [\n \"Bind words to a specified context\"\n words\n context [context!]\n options:\n copy: no [logic!] \"Bind a (deep) copy of WORDS\"\n new: no [logic!] \"Add all words to CONTEXT\"\n return: words\n ]\n tail [\n \"Return the series at the tail position\"\n series [series!]\n return: series\n ]\n print [\n \"Write the string to standard output\"\n string [string!]\n return: string [string!]\n ]\n prin [\n \"Write the string to standard output (do not add a new line)\"\n string [string!]\n return: string [string!]\n ]\n load-string [\n \"Load the string into a Topaz value\"\n string [string!] \"Text representation of a Topaz value\"\n options:\n all: no [logic!] \"Always return a BLOCK! value\"\n return: value\n ]\n read [\n \"Read a file into a string\"\n file-name [file!]\n return: file-contents [string!]\n ]\n try [\n \"Try to evaluate CODE; if it causes an error, evaluate DEFAULT with WORD set to the ERROR! value\"\n code [block!]\n word [any-word!] \"Local to the DEFAULT block\"\n default [block!]\n return: result \"Either the result of evaluating CODE or that of evaluating DEFAULT\"\n ]\n add [\n \"Add val1 to val2\"\n val1 [number!]\n val2 [number!]\n return: sum [number!]\n ]\n subtract [\n \"Subtract val2 from val1\"\n val1 [number!]\n val2 [number!]\n return: difference [number!]\n ]\n equal? [\n \"Return TRUE if the two values are equal\"\n val1\n val2\n return: val1-eq-val2 [logic!]\n ]\n greater? [\n \"Return TRUE if val1 is greater than val2\"\n val1 [number!]\n val2 [number!]\n return: val1-gt-val2 [logic!]\n ]\n lesser? [\n \"Return TRUE if val1 is lesser than val2\"\n val1 [number!]\n val2 [number!]\n return: val1-lt-val2 [logic!]\n ]\n not-equal? [\n \"Return TRUE if the two values are NOT equal\"\n val1\n val2\n return: val1-neq-val2 [logic!]\n ]\n greater-or-equal? [\n \"Return TRUE if val1 is greater or equal to val2\"\n val1 [number!]\n val2 [number!]\n return: val1-geq-val2 [logic!]\n ]\n lesser-or-equal? [\n \"Return TRUE if val1 is lesser or equal to val2\"\n val1 [number!]\n val2 [number!]\n return: val1-leq-val2 [logic!]\n ]\n compile [\n \"Compile BLOCK to Javascript\"\n block [block!]\n return: javascript [string!]\n ]\n write [\n \"Write a string into a file\"\n file-name [file!]\n text [string!]\n return: text [string!]\n ]\n throw [\n \"Throw a value to be catched by an enclosing CATCH\"\n value\n ]\n if [\n \"Evaluate BODY if CONDITION is not NONE or FALSE\"\n condition\n body [block!]\n return: result \"Result of BODY, or NONE if it is not evaluated\"\n ]\n either [\n \"Evaluate TRUE-BODY if CONDITION is not NONE or FALSE, otherwise evaluate FALSE-BODY\"\n condition\n true-body [block!]\n false-body [block!]\n return: result \"Result of either TRUE-BODY or FALSE-BODY depending on CONDITION\"\n ]\n not [\n \"Return TRUE if VALUE is NONE or FALSE; otherwise return FALSE\"\n value\n return: not-value [logic!]\n ]\n apply [\n \"Apply a function to the specified arguments\"\n func [any-function!]\n args [object! context! block!] \"Object or block in the format [arg-name: value ...]\"\n options:\n only: no [logic!] \"(ARGS must be BLOCK!) Apply the contents of args as-is as the list of arguments\"\n return: result \"Result of evaluating FUNC\"\n ]\n while [\n \"Evaluate BODY repeatedly as long as CONDITION is not FALSE or NONE\"\n condition [block!] \"Evaluated using ALL\"\n body [block!]\n options:\n any: no [logic!] \"Evaluate CONDITION using ANY instead of ALL\"\n return: result \"Result of BODY, or NONE if BODY is never evaluated\"\n ]\n all [\n \"Evaluate BLOCK and return the first FALSE or NONE (shortcut AND)\"\n block [block!]\n return: result \"If all expressions are true, the last value is returned\"\n ]\n any [\n \"Evaluate BLOCK and return the first value that is not FALSE or NONE (shortcut OR)\"\n block [block!]\n return: result \"If all expressions are false, NONE is returned\"\n ]\n switch-default [\n \"If VALUE is found in CASES, the block that follows it is evaluated\"\n value\n cases [block!] \"Eg. [a b [...] c [...] ...]\"\n default [block!] \"Block to evaluate if VALUE is not found\"\n options:\n all: no [logic!] \"Evaluate all matches, not just the first one\"\n return: result \"Result of evaluating the selected case block, or DEFAULT\"\n ]\n case [\n \"Evaluate the block that follows the first true (not NONE or FALSE) condition\"\n cases [block!] \"Eg. [a = b [...] c = d [...] ...]\"\n options:\n all: no [logic!] \"Do not stop at the first true condition\"\n return: result \"Result of evaluating the selected block, or NONE\"\n ]\n context-of [\n \"Return the context of a word or function\"\n word [any-word! function!]\n return: context [context!]\n ]\n spec-of [\n \"Return the arguments specification of a function\"\n f [any-function!]\n return: spec [block!]\n ]\n body-of [\n \"Return the body of a function\"\n f [function!]\n return: body [block!]\n ]\n rejoin [\n \"Reduce and join all the strings in the block\"\n block [block!]\n return: joined-strings [string!]\n ]\n type-of [\n \"Return the type of a value\"\n value\n return: type [datatype!]\n ]\n find [\n \"Find VALUE in SERIES\"\n series [series!]\n value\n return: series \"SERIES at the position of VALUE, or at its tail position if it's not found\"\n ]\n until [\n \"Evaluate BODY repeatedly while it returns NONE or FALSE\"\n body [block!]\n return: result \"Result of evaluating BODY\"\n ]\n copy [\n \"Make a copy of a value\"\n value\n return: value \"Copy of VALUE\"\n ]\n cause [\n \"Cause an error\"\n error [error!]\n ]\n catch [\n \"Evaluate CODE and catch any value that is THROWn\"\n code [block!]\n return: result \"A value passed to THROW, or the result of CODE\"\n ]\n form-error [\n \"Format the error into a human readable string\"\n error [error!]\n return: string [string!]\n ]\n in? [\n \"Check if VALUE is in CONTAINER\"\n container \"Eg. object, context, typeset...\"\n value\n return: found \"LOGIC! for OBJECT! and TYPESET!, WORD! or NONE! for CONTEXT!, etc.\"\n ]\n quit [\n \"Quit the Topaz interpreter\"\n exit-code [number!] \"Code to return to calling environment\"\n ]\n slice [\n \"Copy part of a series\"\n start [series!]\n end-or-length [series! number!] \"If series, needs to be same as START in a different position\"\n return: new-series [series!] \"Same type as START\"\n ]\n clear [\n \"Remove all the elements from a series\"\n series [series!]\n return: series\n ]\n get-compiler-value [\n \"Internal\"\n word [any-word!]\n return: value\n ]\n set-compiler-value [\n \"Internal\"\n word [any-word!]\n value\n return: value\n ]\n function [spec locals body]\n make-struct [spec]\n make-array []\n poke-array [array position value]\n pick-array [array position]\n regexp [exp flags]\n to-char [number]\n js-error? [value]\n] [\n set native make native! reduce [spec native]\n]\nfunc: make function! [\n [\n \"Make a function\"\n spec [block!] \"Function arguments specification\"\n body [block!]\n return: function [function!]\n ] [\n make function! reduce [spec body]\n ]\n]\nforeach [op f] [\n + add\n - subtract\n = equal?\n > greater?\n < lesser?\n <> not-equal?\n >= greater-or-equal?\n <= lesser-or-equal?\n] [\n set op make op! get f\n]\nin: make op! func [value container] [in? container value]\ndo: func [\n \"Evaluate a block or file\"\n block [block! paren! file!] \"If FILE!, it must have a Topaz header\"\n return: result\n] [\n if file? block [\n block: load/all block\n ]\n do-block block\n]\naction: func [\n \"Make an ACTION! value\"\n args-spec [block!]\n body-spec [block!]\n return: action [action!]\n] [\n make action! reduce [args-spec body-spec]\n]\nload: action [\n \"Load a file or string into a Topaz value\"\n source [file! string!] \"If FILE!, it must have a Topaz header\"\n options:\n header: no [logic!] \"Also return the Topaz header (only when loading a file)\"\n all: no [logic!] \"Always return a BLOCK! value\"\n return: value\n] [\n file!: [\n contents: read source\n either any [\n \"Topaz [\" = slice contents 7\n found? contents: find contents \"^/Topaz [\"\n ] [\n header-object: object none []\n parse contents: load-string contents [\n 'Topaz\n into [\n any [\n name: set-word!\n value: skip\n (header-object/(name): :value)\n ]\n end\n ]\n |\n (cause-error [\n category: 'Script\n id: 'invalid-header\n message: \"File has an invalid Topaz header\"\n args: source\n ])\n ]\n contents/1: header-object\n case [\n header [\n next contents\n ]\n system/words/all [not all 3 = length-of contents] [\n contents/2\n ]\n 'else [\n skip contents 2\n ]\n ]\n ] [\n cause-error [\n category: 'Script\n id: 'no-header\n message: \"File is missing a Topaz header\"\n args: source\n ]\n ]\n ]\n string!: [load-string/options [string: source all: all]]\n]\nsave: func [\n \"Save a Topaz value into a file (so that it can be loaded back with LOAD)\"\n destination [file!]\n header [object! block!]\n value\n return: value\n] [\n if block? header [header: object none header]\n write destination rejoin [\n \"Topaz \" mold/only header\n \"^/^/\"\n mold/options [\n value: value\n only: all [block? value 1 < length-of value]\n ]\n ]\n]\ncause-error: func [\n \"Cause an error according to the given spec\"\n spec [block! string!]\n] [\n cause make error! either block? spec [spec] [[\n category: 'User\n id: 'message\n message: spec\n ]]\n]\nswitch: func [\n \"If VALUE is found in CASES, the block that follows it is evaluated\"\n value\n cases [block!] \"Eg. [a b [...] c [...] ...]\"\n options:\n all: no [logic!] \"Evaluate all matches, not just the first one\"\n return: result \"Result of evaluating the selected case block, or NONE\"\n] [\n switch-default/options [\n value: value\n cases: cases\n default: []\n all: all\n ]\n]\nfirst: func [series] [pick series 0]\nsecond: func [series] [pick series 1]\ntail?: empty?: func [series] [0 = length-of series]\nnext: func [series] [skip series 1]\nq: func [] [quit 0]\nprobe: func [\n \"Print a text representation of a value; return the value\"\n value\n options:\n limit [number! none!] \"Limit the length of the printed text\"\n indent: \"\" [string!] \"Add this string after each new line\"\n] [\n print mold/options [\n value: :value\n limit: limit\n indent: indent\n ]\n :value\n]\nappend: func [\n \"Append a value at the tail of the series\"\n series [series!]\n value\n options:\n only: no [logic!] \"Append series as a single value\"\n return: series \"SERIES at its head position\"\n] [\n head insert/options [\n series: tail series\n value: :value\n only: only\n ]\n]\ncontext: func [\n \"Make a CONTEXT! value\"\n code [block!]\n return: context [context!]\n] [\n make context! code\n]\nobject: func [\n \"Make an OBJECT! value\"\n parent [object! none!] \"Inherit from this object\"\n code [block!] \"Eg. [a: ... b: ...]\"\n return: object [object!]\n] [\n make object! reduce [parent code]\n]\nfound?: func [\n \"Return TRUE if FIND found the value in the series\"\n series [series!]\n return: found [logic!]\n] [\n not tail? series\n]\ncompose: func [\n \"Create a new block replacing all parens with their value\"\n block [block!] \"Block to copy from\"\n options:\n deep: no [logic!] \"Recurse into sub-blocks\"\n only: no [logic!] \"Insert series as a single value\"\n return: copy-of-block [block!]\n] [\n parse block rule: [\n collect any [\n code: paren! either (only) [keep/only (do-block code)] [keep (do-block code)]\n |\n if (deep) [keep/only into rule]\n |\n keep/only *\n ]\n ]\n]\nmap: func [\n \"Repeat the body over all values in the series replacing them with each result\"\n words [word! block!] \"Word or words to set to each value\"\n series [series!]\n body [block!] \"Each result replaces each value or values\"\n return: series \"The modified series\"\n] [\n _tmp: series\n foreach words series compose/only [\n _tmp/0: do (body)\n _tmp: next _tmp\n ]\n clear _tmp\n series\n]\nattempt: func [\n \"Attempt to evaluate CODE, return NONE in case of error\"\n code [block! paren!]\n] [\n try code 'error [none]\n]\nset-word?: func [value] [set-word! = type-of :value]\nword?: func [value] [word! = type-of :value]\nblock?: func [value] [block! = type-of :value]\nobject?: func [value] [object! = type-of :value]\nfile?: func [value] [file! = type-of :value]\nop?: func [value] [op! = type-of :value]\nany-word?: func [value] [in? any-word! type-of :value]\nany-path?: func [value] [in? any-path! type-of :value]\nany-block?: func [value] [in? any-block! type-of :value]\nany-function?: func [value] [in? any-function! type-of :value]\nsystem: object none [\n name: \"Topaz\"\n version: \"alpha\"\n words: this-context\n datatypes: datatypes\n]\nparse: func [\n \"Parse BLOCK according to RULES\"\n block [block!]\n rules [block!]\n return: result \"Last result from RULES if it matches, NONE otherwise\"\n] [\n set [match?: result:] parse* block rules none\n if match? [result]\n]\nparse*: func [\n \"Parse BLOCK according to RULES (low level)\"\n block [block!]\n rules [block!]\n collection [block! object! none!] \"Collect results here (eg. KEEP)\"\n return:\n block [block!] \"New block position\"\n rules [block!] \"New rules position\"\n match? [logic!] \"Did the rules match?\"\n result \"Parsing result (valid if MATCH? is TRUE)\"\n] [\n match?: no\n result: none\n orig-block: block\n while [\n not empty? rules\n '| <> first rules\n ] [\n rule: first rules\n set this-context apply :parse-step this-context\n if not match? [\n block: orig-block\n rules: next find rules '|\n ]\n ]\n apply :return this-context\n]\nparse-step: action [\n \"Try to match a rule value\"\n rule\n block [block!]\n rules [block!]\n collection [block! object! none!] \"Collect results here (eg. KEEP)\"\n return:\n block [block!] \"New block position\"\n rules [block!] \"New rules position\"\n match? [logic!] \"Did the rules match?\"\n result \"Parsing result (valid if MATCH? is TRUE)\"\n] [\n word!: [\n switch-default rule [\n collect [\n rules: next rules\n rule: first rules\n collection: make block! none\n set this-context apply :parse-element this-context\n result: collection\n apply :return this-context\n ]\n copy [\n orig-block: block\n set this-context parse-element [\n rule: second rules\n block: block\n rules: next rules\n collection: none\n ]\n result: slice orig-block block\n apply :return this-context\n ]\n keep [\n only: no\n apply :parse-keep this-context\n ]\n object [\n rules: next rules\n rule: first rules\n collection: object none []\n set this-context apply :parse-element this-context\n result: collection\n apply :return this-context\n ]\n if [\n rules: next rules\n rule: first rules\n set this-context apply :parse-element this-context\n either :result [\n rule: first rules\n apply :parse-step this-context\n ] [\n match?: no\n result: none\n apply :return this-context\n ]\n ]\n either [\n rules: next orig-rules: rules\n rule: first rules\n set this-context apply :parse-element this-context\n if not all [\n block? true-rule: first rules\n block? false-rule: second rules\n ] [\n cause-error [\n category: 'Parse\n id: 'invalid-rule\n message: \"EITHER must be followed by a condition and two blocks\"\n stack: orig-rules\n ]\n ]\n set [block match?: result:] parse* block either :result [true-rule] [false-rule] collection\n rules: skip rules 2\n apply :return this-context\n ]\n ] [\n apply :parse-element this-context\n ]\n ]\n set-word!: [\n word: rule\n rules: next rules\n rule: first rules\n match?: no\n set this-context apply :parse-step this-context\n if match? [\n either object? collection [\n collection/(word): result\n ] [\n set word result\n ]\n ]\n apply :return this-context\n ]\n path!: [\n either rule = 'keep/only [\n only: yes\n apply :parse-keep this-context\n ] [\n apply :parse-element this-context\n ]\n ]\n default: [\n apply :parse-element this-context\n ]\n]\nparse-keep: func [\n \"Handle KEEP and KEEP/ONLY\"\n block [block!]\n rules [block!]\n collection [block! object! none!] \"Collect results here (eg. KEEP)\"\n only [logic!] \"If true, keep a series as a single value\"\n return:\n block [block!] \"New block position\"\n rules [block!] \"New rules position\"\n match? [logic!] \"Did the rules match?\"\n result \"Parsing result (valid if MATCH? is TRUE)\"\n] [\n match?: no\n result: none\n if not block? collection [\n cause-error [\n category: 'Parse\n id: 'keep-outside-collect\n message: \"KEEP used outside the scope of a COLLECT\"\n stack: rules\n ]\n ]\n set this-context parse-step [\n rule: second rules\n block: block\n rules: next rules\n collection: none\n ]\n if match? [\n insert/options [\n series: tail collection\n value: result\n only: only\n ]\n ]\n apply :return this-context\n]\nparse-element: action [\n \"Try to match a rule element value\"\n rule\n block [block!]\n rules [block!]\n collection [block! object! none!] \"Collect results here (eg. KEEP)\"\n return:\n block [block!] \"New block position\"\n rules [block!] \"New rules position\"\n match? [logic!] \"Did the rules match?\"\n result \"Parsing result (valid if MATCH? is TRUE)\"\n] [\n word!: [\n switch-default rule [\n opt [\n rules: next rules\n rule: first rules\n set this-context apply :parse-element this-context\n match?: yes\n apply :return this-context\n ]\n literal [\n rules: next rules\n rule: first rules\n condition: [rule = first block]\n apply :parse-match this-context\n ]\n any [\n rules: next rules\n rule: first rules\n apply :parse-any this-context\n ]\n some [\n match?: no\n rules: any-rules: next rules\n rule: first rules\n set this-context apply :parse-element this-context\n if match? [\n rules: any-rules\n set this-context apply :parse-any this-context\n ]\n apply :return this-context\n ]\n skip * [\n match?: not empty? block\n result: if match? [first block]\n block: next block\n rules: next rules\n apply :return this-context\n ]\n end [\n return [\n block: block\n rules: next rules\n match?: empty? block\n result: none\n ]\n ]\n here [\n return [\n block: block\n rules: next rules\n match?: yes\n result: block\n ]\n ]\n into [\n rule: second rules\n if word? rule [rule: get rule]\n if not block? rule [\n cause-error [\n category: 'Parse\n id: 'invalid-rule\n message: \"Invalid argument to INTO (must be BLOCK!)\"\n args: rule\n stack: rules\n ]\n ]\n either all [not empty? block block? first block] [\n result: parse* first block rule collection\n return [\n block: either result/match? [next block] [block]\n rules: skip rules 2\n match?: result/match?\n result: result/result\n ]\n ] [\n return [\n block: block\n rules: skip rules 2\n match?: no\n result: none\n ]\n ]\n ]\n ] [\n rule: get rule\n apply :parse-element this-context\n ]\n ]\n paren!: [\n return [\n block: block\n rules: next rules\n match?: yes\n result: do-block rule\n ]\n ]\n block!: [\n set [block match?: result:] parse* block rule collection\n rules: next rules\n apply :return this-context\n ]\n datatype!: [\n condition: [rule = type-of first block]\n apply :parse-match this-context\n ]\n default: [\n condition: [rule = first block]\n apply :parse-match this-context\n ]\n]\nparse-any: func [\n \"Try to match a rule value repeatedly (as long as it still matches)\"\n rule\n block [block!]\n rules [block!]\n collection [block! object! none!] \"Collect results here (eg. KEEP)\"\n return:\n block [block!] \"New block position\"\n rules [block!] \"New rules position\"\n match? [logic!] \"Did the rules match?\"\n result \"Parsing result (valid if MATCH? is TRUE)\"\n] [\n result: none\n until [\n element: apply :parse-element this-context\n block: element/block\n either element/match? [result: element/result false] [true]\n ]\n return [\n block: block\n rules: element/rules\n match?: yes\n result: result\n ]\n]\nparse-match: func [\n \"Match a condition\"\n rule\n block [block!]\n rules [block!]\n collection [block! object! none!] \"Collect results here (eg. KEEP)\"\n condition [block!]\n return:\n block [block!] \"New block position\"\n rules [block!] \"New rules position\"\n match? [logic!] \"Did the rules match?\"\n result \"Parsing result (valid if MATCH? is TRUE)\"\n] [\n either all [not empty? block do-block condition] [\n return [\n block: next block\n rules: next rules\n match?: yes\n result: first block\n ]\n ] [\n return [\n block: block\n rules: next rules\n match?: no\n result: none\n ]\n ]\n]\ncompiler-keywords: context [\n none: make native! [[] none]\n true: make native! [[] true]\n false: make native! [[] false]\n switch: make native! [[value cases options: all: no [logic!]] switch]\n]"), true));
} catch (e) {
print("FATAL: error during initialization");
e = handleJsError(e);
if (e.type.name != "error!") {
e = make(error_type, {
category: "Internal",
id: "unhandled-throw",
message: "Unhandled throw during initialization",
args: e.type
});
}
print(formError(e));
process.exit(1);
}
handleTopLevelError = function(e) {
e = handleJsError(e);
if (e.type.name != "error!") {
e = make(error_type, {
category: "Script",
id: "unhandled-throw",
message: "Unhandled exception (THROW without CATCH)",
args: e.type
});
}
return print(formError(e));
};
if (2 < lengthOfArray(process.argv)) {
try {
_do(append(load(make(string_type, "[do]"), false), make(file_type, process.argv[2])));
} catch (e) {
handleTopLevelError(e);
}
} else {
print("Topaz Interpreter - (C) 2011 Gabriele Santilli - MIT License");
stdin = process.openStdin();
stdin.setEncoding("utf8");
stdin.addListener("data", function(chunk) {
var res;
try {
chunk = _do(load(make(string_type, chunk), true));
prin("== ");
print(mold(chunk, false, false, 100, "== "));
return prin(">> ");
} catch (e) {
handleTopLevelError(e);
return prin(">> ");
}
});
prin(">> ");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment