Created
August 16, 2020 18:40
-
-
Save giesse/3ebac89c91ec8cbb06420f62d6b9dff2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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