commit
d5ccdbea3b
@ -0,0 +1 @@
|
||||
node_modules
|
||||
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2015—2016 Tanasoaia Teodor Andrei
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -0,0 +1,5 @@
|
||||
# lua2js
|
||||
|
||||
A Lua to JS transpiler
|
||||
|
||||
This library is based on the sourcecode of this library: https://github.com/paulcuth/starlight
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "lua2js",
|
||||
"version": "0.1.0",
|
||||
"description": "A Lua to JS transpiler",
|
||||
"main": "dist/lua2js.js",
|
||||
"module": "src/index.js",
|
||||
"author": "Teoxoy",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"printf": "^0.2.5",
|
||||
"luaparse": "^0.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0-beta.42",
|
||||
"@babel/preset-env": "^7.0.0-beta.42",
|
||||
"babel-loader": "^8.0.0-beta.2",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"webpack": "^4.4.1",
|
||||
"webpack-cli": "^2.0.13"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
import * as parser from './parser'
|
||||
import * as runtime from './runtime'
|
||||
|
||||
export {
|
||||
parser,
|
||||
runtime
|
||||
}
|
||||
@ -0,0 +1,508 @@
|
||||
import luaparse from 'luaparse'
|
||||
|
||||
class MemExpr extends String {
|
||||
constructor(base, property) {
|
||||
super();
|
||||
this.base = base;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
get() {
|
||||
return `Tget(${this.base}, ${this.property})`;
|
||||
}
|
||||
|
||||
set(value) {
|
||||
return `Tset(${this.base}, ${this.property}, ${value})`;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.get();
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return this.get();
|
||||
}
|
||||
}
|
||||
|
||||
let scopeIndex = 1;
|
||||
let functionIndex = 0;
|
||||
let forLoopIndex = 0;
|
||||
|
||||
const UNI_OP_MAP = {
|
||||
'-': 'unm',
|
||||
'not': 'not',
|
||||
'#': 'len'
|
||||
};
|
||||
|
||||
const BIN_OP_MAP = {
|
||||
'..': 'concat',
|
||||
'+': 'add',
|
||||
'-': 'sub',
|
||||
'*': 'mul',
|
||||
'/': 'div',
|
||||
'%': 'mod',
|
||||
'==': 'eq',
|
||||
'~=': 'neq',
|
||||
'<': 'lt',
|
||||
'>': 'gt',
|
||||
'<=': 'lte',
|
||||
'>=': 'gte',
|
||||
'^': 'pow'
|
||||
};
|
||||
|
||||
|
||||
const GENERATORS = {
|
||||
|
||||
AssignmentStatement(node, scope) {
|
||||
let assignments = node.variables.map((variable, index) => {
|
||||
const name = scoped(variable, scope);
|
||||
|
||||
if (name instanceof MemExpr) {
|
||||
return name.set(`__star_tmp[${index}]`);
|
||||
} else {
|
||||
const [match, args] = [].concat(name.match(/^\$get\((.*)\)$/));
|
||||
if (!match) {
|
||||
throw new Error('Unhandled');
|
||||
}
|
||||
|
||||
return `$set(${args}, __star_tmp[${index}])`;
|
||||
}
|
||||
}).join(';\n');
|
||||
|
||||
const values = parseExpressionList(node.init, scope).join(', ');
|
||||
return `__star_tmp = [${values}];${assignments}`;
|
||||
},
|
||||
|
||||
|
||||
BinaryExpression(node, scope) {
|
||||
let left = scoped(node.left, scope);
|
||||
let right = scoped(node.right, scope);
|
||||
let operator = BIN_OP_MAP[node.operator];
|
||||
|
||||
if (isCallExpression(node.left)) {
|
||||
left += '[0]';
|
||||
}
|
||||
|
||||
if (isCallExpression(node.right)) {
|
||||
right += '[0]';
|
||||
}
|
||||
|
||||
if (!operator) {
|
||||
console.info(node);
|
||||
throw new Error(`Unhandled binary operator: ${node.operator}`);
|
||||
}
|
||||
|
||||
return `__star_op_${operator}(${left}, ${right})`;
|
||||
},
|
||||
|
||||
|
||||
BooleanLiteral(node) {
|
||||
return node.value ? 'true' : 'false';
|
||||
},
|
||||
|
||||
|
||||
BreakStatement(node) {
|
||||
return 'break';
|
||||
},
|
||||
|
||||
|
||||
CallStatement(node, scope) {
|
||||
return generate(node.expression, scope);
|
||||
},
|
||||
|
||||
|
||||
CallExpression(node, scope) {
|
||||
let functionName = scoped(node.base, scope);
|
||||
const args = parseExpressionList(node.arguments, scope);
|
||||
|
||||
if (isCallExpression(node.base)) {
|
||||
args.unshift(`${functionName}[0]`);
|
||||
} else {
|
||||
if (functionName instanceof MemExpr && node.base.indexer === ':') {
|
||||
args.unshift(functionName.base);
|
||||
}
|
||||
args.unshift(`${functionName}`);
|
||||
}
|
||||
|
||||
return `__star_call(${args})`;
|
||||
},
|
||||
|
||||
|
||||
Chunk(node, scope) {
|
||||
let output = node.body.map(statement => generate(statement, scope) + ';');
|
||||
return output.join('\n');
|
||||
},
|
||||
|
||||
|
||||
DoStatement(node, outerScope) {
|
||||
let { scope, scopeDef } = extendScope(outerScope);
|
||||
let body = this.Chunk(node, scope);
|
||||
scopeDef = scopeDef.replace(',', ';');
|
||||
return `${scopeDef}\n${body}\n$=$${outerScope};`;
|
||||
},
|
||||
|
||||
|
||||
ElseClause(node, scope) {
|
||||
let body = this.Chunk(node, scope);
|
||||
return `{\n${body}\n}`;
|
||||
},
|
||||
|
||||
|
||||
ElseifClause(node, scope) {
|
||||
return this.IfClause(node, scope);
|
||||
},
|
||||
|
||||
|
||||
ForNumericStatement(node, outerScope) {
|
||||
let { scope, scopeDef } = extendScope(outerScope);
|
||||
let variableName = generate(node.variable, outerScope);
|
||||
let start = scoped(node.start, outerScope);
|
||||
let end = scoped(node.end, outerScope);
|
||||
let step = node.step === null ? 1 : generate(node.step, outerScope);
|
||||
let operator = step > 0 ? '<=' : '>=';
|
||||
let body = this.Chunk(node, scope);
|
||||
let loopIndex = ++forLoopIndex;
|
||||
|
||||
let init = `$${outerScope}._forLoop${loopIndex} = ${start}`;
|
||||
let cond = `$${outerScope}._forLoop${loopIndex} ${operator} ${end}`;
|
||||
let after = `$${outerScope}._forLoop${loopIndex} += ${step}`;
|
||||
let varInit = `$${scope}.setLocal('${variableName}',$${outerScope}._forLoop${loopIndex});`;
|
||||
return `for (${init}; ${cond}; ${after}) {\n${scopeDef}\n${varInit}\n${body}\n}`;
|
||||
},
|
||||
|
||||
|
||||
ForGenericStatement(node, outerScope) {
|
||||
const { scope, scopeDef } = extendScope(outerScope);
|
||||
const { scope: iterationScope, scopeDef: iterationScopeDef } = extendScope(scope);
|
||||
const iterators = parseExpressionList(node.iterators, outerScope).join(', ');
|
||||
const body = this.Chunk(node, iterationScope);
|
||||
|
||||
const variables = node.variables.map((variable, index) => {
|
||||
const name = generate(variable, scope);
|
||||
return `$setLocal($, '${name}', __star_tmp[${index}])`;
|
||||
}).join(';\n');
|
||||
|
||||
const defs = scopeDef.split(', ');
|
||||
return `${defs[0]};\n[$${scope}._iterator, $${scope}._table, $${scope}._next] = [${iterators}];\nwhile((__star_tmp = __star_call($${scope}._iterator, $${scope}._table, $${scope}._next)),__star_tmp[0] !== undefined) {\n${iterationScopeDef}\$${scope}._next = __star_tmp[0]\n${variables}\n${body}\n}`;
|
||||
},
|
||||
|
||||
|
||||
FunctionDeclaration(node, outerScope) {
|
||||
let { scope, scopeDef } = extendScope(outerScope);
|
||||
let isAnonymous = !node.identifier;
|
||||
let identifier = isAnonymous ? '' : generate(node.identifier, outerScope);
|
||||
let isMemberExpr = identifier instanceof MemExpr;
|
||||
|
||||
let params = node.parameters.map((param, index) => {
|
||||
let name = generate(param, scope);
|
||||
if (name === '...$.getVarargs()') {
|
||||
return `$.setVarargs(args)`;
|
||||
} else {
|
||||
return `$setLocal($, '${name}', __star_shift(args))`;
|
||||
}
|
||||
});
|
||||
|
||||
let name;
|
||||
if (isMemberExpr) {
|
||||
name = identifier.property.replace(/'/g, '');
|
||||
|
||||
if (node.identifier.indexer === ':') {
|
||||
params.unshift("$setLocal($, 'self', __star_shift(args))");
|
||||
}
|
||||
} else {
|
||||
name = identifier;
|
||||
}
|
||||
|
||||
let paramStr = params.join(';\n');
|
||||
let body = this.Chunk(node, scope);
|
||||
let prefix = isAnonymous? '' : 'func$';
|
||||
let funcDef = `(__star_tmp = function ${prefix}${name}(...args){${scopeDef}\n${paramStr};\n${body} return [];}, __star_tmp.toString=()=>'function: 0x${(++functionIndex).toString(16)}', __star_tmp)`;
|
||||
|
||||
if (isAnonymous) {
|
||||
return funcDef;
|
||||
} else if (isMemberExpr) {
|
||||
return identifier.set(funcDef);
|
||||
} else {
|
||||
const local = node.isLocal ? 'Local' : '';
|
||||
return `$set${local}($, '${identifier}', ${funcDef})`;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Identifier(node, scope) {
|
||||
return node.name;
|
||||
},
|
||||
|
||||
|
||||
IfClause(node, scope) {
|
||||
let condition = scoped(node.condition, scope);
|
||||
|
||||
if (isCallExpression(node.condition)) {
|
||||
condition += '[0]';
|
||||
}
|
||||
|
||||
let body = this.Chunk(node, scope);
|
||||
return `if (__star_op_bool(${condition})) {\n${body}\n}`;
|
||||
},
|
||||
|
||||
|
||||
IfStatement(node, scope) {
|
||||
let clauses = node.clauses.map((clause) => generate(clause, scope));
|
||||
return clauses.join (' else ');
|
||||
},
|
||||
|
||||
|
||||
IndexExpression(node, scope) {
|
||||
let base = scoped(node.base, scope);
|
||||
let index = scoped(node.index, scope);
|
||||
|
||||
if (isCallExpression(node.base)) {
|
||||
base += '[0]';
|
||||
}
|
||||
|
||||
if (isCallExpression(node.index)) {
|
||||
index += '[0]';
|
||||
}
|
||||
|
||||
return new MemExpr(base, index);
|
||||
},
|
||||
|
||||
|
||||
LocalStatement(node, scope) {
|
||||
let assignments = node.variables.map((variable, index) => {
|
||||
let name = generate(variable, scope);
|
||||
return `$setLocal($, '${name}', __star_tmp[${index}])`;
|
||||
}).join(';\n');
|
||||
|
||||
const values = parseExpressionList(node.init, scope).join(', ');
|
||||
return `__star_tmp = [${values}];${assignments}`;
|
||||
},
|
||||
|
||||
|
||||
LogicalExpression(node, scope) {
|
||||
let left = scoped(node.left, scope);
|
||||
let right = scoped(node.right, scope);
|
||||
let operator = node.operator;
|
||||
|
||||
if (isCallExpression(node.left)) {
|
||||
left += '[0]';
|
||||
}
|
||||
|
||||
if (isCallExpression(node.right)) {
|
||||
right += '[0]';
|
||||
}
|
||||
|
||||
if (operator === 'and') {
|
||||
return `(!__star.op.bool(${left})?${left}:${right})`
|
||||
|
||||
} else if (operator === 'or') {
|
||||
return `(__star.op.bool(${left})?${left}:${right})`
|
||||
|
||||
} else {
|
||||
console.info(node);
|
||||
throw new Error(`Unhandled logical operator: ${node.operator}`);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
MemberExpression(node, scope) {
|
||||
let base = scoped(node.base, scope);
|
||||
let identifier = generate(node.identifier, scope);
|
||||
|
||||
if (isCallExpression(node.base)) {
|
||||
base += '[0]';
|
||||
}
|
||||
|
||||
return new MemExpr(base, `'${identifier}'`);
|
||||
},
|
||||
|
||||
|
||||
NilLiteral(node) {
|
||||
return 'undefined';
|
||||
},
|
||||
|
||||
|
||||
NumericLiteral(node) {
|
||||
return node.value.toString();
|
||||
},
|
||||
|
||||
|
||||
RepeatStatement(node, outerScope) {
|
||||
let { scope, scopeDef } = extendScope(outerScope);
|
||||
let condition = scoped(node.condition, outerScope);
|
||||
let body = this.Chunk(node, scope);
|
||||
|
||||
return `do{\n${scopeDef}\n${body}\n}while(!(${condition}))`;
|
||||
},
|
||||
|
||||
|
||||
ReturnStatement(node, scope) {
|
||||
const args = parseExpressionList(node.arguments, scope).join(', ');
|
||||
return `return [${args}];`;
|
||||
},
|
||||
|
||||
|
||||
StringCallExpression(node, scope) {
|
||||
node.arguments = node.argument;
|
||||
return this.TableCallExpression(node, scope);
|
||||
},
|
||||
|
||||
|
||||
StringLiteral(node) {
|
||||
let raw = node.raw;
|
||||
if (/^\[\[[^]*]$/m.test(raw)) {
|
||||
return `\`${raw.substr(2, raw.length - 4).replace(/\\/g, '\\\\')}\``;
|
||||
} else {
|
||||
raw = raw.replace(/([^\\])\\(\d{1,3})/g, (_, pre, dec) => `${pre}\\u${('000' + parseInt(dec, 10).toString(16)).substr(-4)}`);
|
||||
return raw;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
TableCallExpression(node, scope) {
|
||||
let functionName = scoped(node.base, scope);
|
||||
let args = [generate(node.arguments, scope)];
|
||||
|
||||
if (isCallExpression(node.base)) {
|
||||
return `__star_call(${functionName}[0],${args})`;
|
||||
} else {
|
||||
if (functionName instanceof MemExpr && node.base.indexer === ':') {
|
||||
args.unshift(functionName.base);
|
||||
}
|
||||
return `__star_call(${functionName},${args})`;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
TableConstructorExpression(node, scope) {
|
||||
let fields = node.fields.map((field, index, arr) => {
|
||||
if (field.type == 'TableValue') {
|
||||
const isLastItem = index === arr.length - 1;
|
||||
return this.TableValue(field, scope, isLastItem);
|
||||
}
|
||||
return generate(field, scope);
|
||||
}).join(';\n');
|
||||
|
||||
return `new __star_T(t => {${fields}})`;
|
||||
},
|
||||
|
||||
|
||||
TableKeyString(node, scope) {
|
||||
let name = generate(node.key, scope);
|
||||
let value = scoped(node.value, scope);
|
||||
return `Tset(t, '${name}', ${value})`;
|
||||
},
|
||||
|
||||
|
||||
TableKey(node, scope) {
|
||||
let name = scoped(node.key, scope);
|
||||
let value = scoped(node.value, scope);
|
||||
return `Tset(t, ${name}, ${value})`;
|
||||
},
|
||||
|
||||
|
||||
TableValue(node, scope, isLastItem) {
|
||||
let value = scoped(node.value, scope);
|
||||
if (isCallExpression(node.value)) {
|
||||
value = isLastItem ? `...${value}` : `${value}[0]`;
|
||||
}
|
||||
return `Tins(t, ${value})`;
|
||||
},
|
||||
|
||||
|
||||
UnaryExpression(node, scope) {
|
||||
let operator = UNI_OP_MAP[node.operator];
|
||||
let argument = scoped(node.argument, scope);
|
||||
|
||||
if (isCallExpression(node.argument)) {
|
||||
argument += '[0]';
|
||||
}
|
||||
|
||||
if (!operator) {
|
||||
console.info(node);
|
||||
throw new Error(`Unhandled unary operator: ${node.operator}`);
|
||||
}
|
||||
|
||||
return `__star_op_${operator}(${argument})`;
|
||||
},
|
||||
|
||||
|
||||
VarargLiteral(node) {
|
||||
return '...$.getVarargs()';
|
||||
},
|
||||
|
||||
|
||||
WhileStatement(node, outerScope) {
|
||||
let { scope, scopeDef } = extendScope(outerScope);
|
||||
let condition = scoped(node.condition, outerScope);
|
||||
let body = this.Chunk(node, scope);
|
||||
|
||||
return `while(${condition}) {\n${scopeDef}\n${body}\n}`;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
function parseExpressionList(expressionNodeArray, scope) {
|
||||
return expressionNodeArray.map((node, index, arr) => {
|
||||
let value = scoped(node, scope);
|
||||
if (isCallExpression(node)) {
|
||||
if (index == arr.length - 1) {
|
||||
return `...${value}`;
|
||||
}
|
||||
return `${value}[0]`;
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function extendScope(outerIndex) {
|
||||
let scope = scopeIndex++;
|
||||
let scopeDef = `let $${scope} = $${outerIndex}.extend(), $ = $${scope};`;
|
||||
return { scope, scopeDef };
|
||||
}
|
||||
|
||||
|
||||
function scoped(node, scope) {
|
||||
let value = generate(node, scope);
|
||||
return node.type === 'Identifier' ? `$get($, '${value}')` : value;
|
||||
}
|
||||
|
||||
|
||||
function isCallExpression(node) {
|
||||
return !!node.type.match(/CallExpression$/);
|
||||
}
|
||||
|
||||
|
||||
function generate(ast, scope) {
|
||||
let generator = GENERATORS[ast.type];
|
||||
|
||||
if (!generator) {
|
||||
console.info(ast);
|
||||
throw new Error(`No generator found for: ${ast.type}`);
|
||||
}
|
||||
|
||||
return generator.call(GENERATORS, ast, scope);
|
||||
}
|
||||
|
||||
export function parse(data) {
|
||||
let ast = luaparse.parse(data);
|
||||
let js = generate(ast, 0);
|
||||
return `
|
||||
"use strict"; if (typeof global === \'undefined\' && typeof window !== \'undefined\') { window.global = window; }
|
||||
let __star = global.starlight.runtime, $0 = __star.globalScope, $ = $0, __star_tmp;
|
||||
let __star_call = __star.call, __star_T = __star.T, __star_op_bool = __star.op.bool;
|
||||
let __star_op_unm = __star.op.unm, __star_op_not = __star.op.not, __star_op_len = __star.op.len, __star_op_concat = __star.op.concat, __star_op_add = __star.op.add, __star_op_sub = __star.op.sub, __star_op_mul = __star.op.mul, __star_op_div = __star.op.div, __star_op_mod = __star.op.mod, __star_op_eq = __star.op.eq, __star_op_neq = __star.op.neq, __star_op_lt = __star.op.lt, __star_op_gt = __star.op.gt, __star_op_lte = __star.op.lte, __star_op_gte = __star.op.gte, __star_op_pow = __star.op.pow;
|
||||
let __star_op_and = __star.op.and, __star_op_or = __star.op.or;
|
||||
let Tget, Tset, Tins, $get, $set, $setLocal, __star_shift;
|
||||
(()=>{
|
||||
let call = Function.prototype.call, bind = call.bind.bind(call), Tproto = __star_T.prototype, $proto = __star.globalScope.constructor.prototype;
|
||||
Tget = bind(Tproto.get), Tset = bind(Tproto.set), Tins = bind(Tproto.insert);
|
||||
$get = bind($proto.get), $set = bind($proto.set), $setLocal = bind($proto.setLocal);
|
||||
__star_shift = bind(Array.prototype.shift);
|
||||
})();
|
||||
${js}
|
||||
`
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
export default class LuaError extends Error {
|
||||
constructor(message) {
|
||||
super();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `LuaError: ${this.message}`;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
export default class Scope {
|
||||
constructor(variables = {}) {
|
||||
this._variables = variables;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this._variables[key];
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
let vars = this._variables;
|
||||
|
||||
if (hasOwnProperty.call(this._variables, key) || !this.parent) {
|
||||
vars[key] = value;
|
||||
} else {
|
||||
this.parent.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
setLocal(key, value) {
|
||||
this._variables[key] = value;
|
||||
}
|
||||
|
||||
setVarargs(value) {
|
||||
this._varargs = value;
|
||||
}
|
||||
|
||||
getVarargs() {
|
||||
return this._varargs || this.parent && this.parent.getVarargs();
|
||||
}
|
||||
|
||||
add(key, value) {
|
||||
this._variables[key] += value;
|
||||
}
|
||||
|
||||
extend(outerScope) {
|
||||
let innerVars = Object.create(this._variables);
|
||||
let scope = new Scope(innerVars);
|
||||
scope.parent = this;
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,214 @@
|
||||
import { default as LuaError } from './LuaError';
|
||||
import { type } from './lib/globals';
|
||||
|
||||
let count = 0;
|
||||
let stringLib, getn;
|
||||
|
||||
export function registerLibs(libs) {
|
||||
// Can't import directly because they'll create a circular dependencies. :(
|
||||
stringLib = libs.string;
|
||||
getn = libs.getn;
|
||||
};
|
||||
|
||||
|
||||
export default class Table {
|
||||
|
||||
constructor(initialiser) {
|
||||
this.index = ++count;
|
||||
this.numValues = [undefined];
|
||||
this.strValues = {};
|
||||
this.keys = [];
|
||||
this.values = [];
|
||||
this.metatable = null;
|
||||
|
||||
if (!initialiser) {
|
||||
// noop
|
||||
} else if (typeof initialiser === 'function') {
|
||||
initialiser(this);
|
||||
} else {
|
||||
let isArr = initialiser instanceof Array;
|
||||
|
||||
for (let i in initialiser) {
|
||||
if (initialiser.hasOwnProperty(i)) {
|
||||
let value = initialiser[i];
|
||||
if (value === null) value = undefined;
|
||||
let key = isArr? parseInt(i, 10) + 1: i;
|
||||
|
||||
let iterate = (typeof value == 'object' && value.constructor === Object) || value instanceof Array;
|
||||
this.set(key, iterate? new Table(value) : value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get(key) {
|
||||
if (!(this instanceof Table)) {
|
||||
if (type(this) == 'string') {
|
||||
return stringLib.get(key);
|
||||
|
||||
} else if (
|
||||
type(this) === 'userdata'
|
||||
|| (type(this) === 'function' && key === 'new') // exception for DOMAPI compat with Moonshine
|
||||
) {
|
||||
if (key in this) {
|
||||
return this[key];
|
||||
}
|
||||
}
|
||||
throw new LuaError(`attempt to index a ${type(this)} value`);
|
||||
}
|
||||
|
||||
let value = this.rawget(key);
|
||||
|
||||
if (value === void 0) {
|
||||
let mt, mm;
|
||||
if (
|
||||
(mt = this.metatable)
|
||||
&& (mm = mt.get('__index'))
|
||||
) {
|
||||
switch (mm.constructor) {
|
||||
case Table: return mm.get(key);
|
||||
case Function:
|
||||
value = mm.call(undefined, this, key);
|
||||
return (value instanceof Array)? value[0] : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
rawget(key) {
|
||||
switch (typeof key) {
|
||||
case 'string': return Object.prototype.hasOwnProperty.call(this.strValues, key) ? this.strValues[key] : void 0;
|
||||
case 'number':
|
||||
if (key > 0 && key == key >> 0) {
|
||||
return this.numValues[key];
|
||||
}
|
||||
/* fallthrough */
|
||||
default:
|
||||
let index = this.keys.indexOf(key);
|
||||
return (index >= 0) ? this.values[index] : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
set(key, value) {
|
||||
if (!(this instanceof Table)) {
|
||||
if (type(this) == 'userdata') {
|
||||
this[key] = value;
|
||||
return;
|
||||
}
|
||||
throw new LuaError(`attempt to index a ${type(this)} value`);
|
||||
}
|
||||
|
||||
let mt, mm;
|
||||
if (
|
||||
(mt = this.metatable)
|
||||
&& (mm = mt.get('__newindex'))
|
||||
) {
|
||||
let oldValue;
|
||||
|
||||
switch (typeof key) {
|
||||
case 'string':
|
||||
oldValue = this.strValues[key];
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
let positiveIntegerKey = key > 0 && key == key >> 0;
|
||||
if (positiveIntegerKey) {
|
||||
oldValue = this.numValues[key];
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
let keys = this.keys;
|
||||
let index = keys.indexOf(key);
|
||||
oldValue = index == -1? undefined : this.values[index];
|
||||
}
|
||||
|
||||
if (oldValue === undefined) {
|
||||
switch (mm.constructor) {
|
||||
case Table: return mm.set(key, value);
|
||||
case Function: return mm(this, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.rawset(key, value);
|
||||
}
|
||||
|
||||
|
||||
rawset(key, value) {
|
||||
if (value instanceof Array) {
|
||||
value = value[0];
|
||||
}
|
||||
|
||||
switch (typeof key) {
|
||||
case 'string':
|
||||
this.strValues[key] = value;
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
let positiveIntegerKey = key > 0 && key == key >> 0;
|
||||
if (positiveIntegerKey) {
|
||||
this.numValues[key] = value;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
let keys = this.keys;
|
||||
let index = keys.indexOf(key);
|
||||
if (index < 0) {
|
||||
index = keys.length;
|
||||
keys[index] = key;
|
||||
}
|
||||
|
||||
this.values[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
insert(...values) {
|
||||
this.numValues.push(...values);
|
||||
}
|
||||
|
||||
|
||||
toString() {
|
||||
let mt, mm;
|
||||
if (
|
||||
(mt = this.metatable)
|
||||
&& (mm = mt.get('__tostring'))
|
||||
) {
|
||||
return mm(this)[0];
|
||||
} else {
|
||||
return 'table: 0x' + this.index.toString(16);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
toObject() {
|
||||
const isArr = getn(this) > 0;
|
||||
const result = isArr? [] : {};
|
||||
const numValues = this.numValues;
|
||||
const strValues = this.strValues;
|
||||
|
||||
let i;
|
||||
const l = numValues.length;
|
||||
|
||||
for (i = 1; i < l; i++) {
|
||||
const propValue = numValues[i];
|
||||
result[i - 1] = (propValue instanceof Table)? propValue.toObject() : propValue;
|
||||
}
|
||||
|
||||
for (i in strValues) {
|
||||
if (strValues.hasOwnProperty(i)) {
|
||||
const propValue = strValues[i];
|
||||
result[i] = (propValue instanceof Table)? propValue.toObject() : propValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
@ -0,0 +1,78 @@
|
||||
import { default as Scope } from './Scope';
|
||||
import { default as globals, type } from './lib/globals';
|
||||
import { default as operators } from './operators';
|
||||
import { default as Table, registerLibs } from './Table';
|
||||
import { default as LuaError } from './LuaError';
|
||||
|
||||
|
||||
function ensureArray(value) {
|
||||
return (value instanceof Array) ? value : [value];
|
||||
}
|
||||
|
||||
function call(f, ...args) {
|
||||
if (!(f instanceof Function)) {
|
||||
if (f instanceof Table) {
|
||||
let mt, mm;
|
||||
if (
|
||||
(mt = f.metatable)
|
||||
&& (mm = mt.rawget('__call'))
|
||||
) {
|
||||
args.unshift(f);
|
||||
f = mm;
|
||||
}
|
||||
}
|
||||
if (!(f instanceof Function)) {
|
||||
let typ = type(f);
|
||||
throw new LuaError(`attempt to call a ${typ} value`);
|
||||
}
|
||||
}
|
||||
|
||||
return ensureArray(f(...args));
|
||||
}
|
||||
|
||||
let namespace = global.starlight = global.starlight || {};
|
||||
let _G = globals;
|
||||
|
||||
function init () {
|
||||
let userEnv = namespace.config && namespace.config.env;
|
||||
if (userEnv) {
|
||||
for (let key in userEnv) {
|
||||
globals.set(key, userEnv[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init();
|
||||
|
||||
let runtime = namespace.runtime = {
|
||||
globalScope: new Scope(globals.strValues),
|
||||
_G,
|
||||
op: operators,
|
||||
T: Table,
|
||||
LuaError,
|
||||
call
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// The following should be configurable
|
||||
|
||||
import { default as math } from './lib/math';
|
||||
_G.set('math', math);
|
||||
|
||||
import { default as table, getn } from './lib/table';
|
||||
_G.set('table', table);
|
||||
|
||||
import { default as string } from './lib/string';
|
||||
_G.set('string', string);
|
||||
|
||||
import { default as os } from './lib/os';
|
||||
_G.set('os', os);
|
||||
|
||||
import { default as _package } from './lib/package';
|
||||
_G.set('package', _package);
|
||||
|
||||
registerLibs({ string, getn });
|
||||
|
||||
@ -0,0 +1,454 @@
|
||||
import { default as T } from '../Table';
|
||||
import { default as LuaError } from '../LuaError';
|
||||
import { default as stringLib, metatable as stringMetatable } from './string';
|
||||
import { getn } from './table';
|
||||
|
||||
import {
|
||||
stdout,
|
||||
coerceToNumber,
|
||||
coerceToString,
|
||||
coerceToBoolean,
|
||||
coerceArgToNumber,
|
||||
coerceArgToString,
|
||||
coerceArgToTable
|
||||
} from '../utils';
|
||||
|
||||
|
||||
const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
|
||||
|
||||
function getPackageMethods() {
|
||||
let packageLib = global.starlight.runtime._G.rawget('package');
|
||||
if (packageLib === void 0) {
|
||||
throw new LuaError('error during require(), package lib not found.');
|
||||
}
|
||||
let methods = [packageLib.rawget('preload'), packageLib.rawget('loaded')];
|
||||
getPackageMethods = ()=>methods;
|
||||
return methods;
|
||||
}
|
||||
|
||||
|
||||
function ipairsIterator(table, index) {
|
||||
if (index === void 0) {
|
||||
throw new LuaError('Bad argument #2 to ipairs() iterator');
|
||||
}
|
||||
|
||||
var nextIndex = index + 1,
|
||||
numValues = table.numValues;
|
||||
|
||||
if (!numValues.hasOwnProperty(nextIndex) || numValues[nextIndex] === void 0) return void 0;
|
||||
return [nextIndex, numValues[nextIndex]];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const _VERSION = 'Lua 5.1';
|
||||
|
||||
|
||||
export function assert(v, m) {
|
||||
if (!coerceToBoolean(v)) {
|
||||
m = coerceArgToString(m, 'assert', 2);
|
||||
throw new LuaError(m || 'Assertion failed!');
|
||||
}
|
||||
return [v, m];
|
||||
}
|
||||
|
||||
|
||||
// TODO dofile([filename])
|
||||
|
||||
|
||||
export function error(message) {
|
||||
if (
|
||||
typeof message !== 'string'
|
||||
&& typeof message !== 'number'
|
||||
) {
|
||||
message = '(error object is not a string)';
|
||||
}
|
||||
throw new LuaError(message);
|
||||
}
|
||||
|
||||
|
||||
// TODO getfenv([f])
|
||||
|
||||
|
||||
export function getmetatable(table) {
|
||||
if (table && table instanceof T) {
|
||||
let mt = table.metatable;
|
||||
if (mt) {
|
||||
let mm;
|
||||
return (mm = mt.rawget('__metatable')) ? mm : mt;
|
||||
}
|
||||
} else if (typeof table == 'string') {
|
||||
return stringMetatable;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function ipairs(t) {
|
||||
t = coerceArgToTable(t, 'ipairs', 1);
|
||||
const mt = getmetatable(t);
|
||||
const mm = mt && mt.get('__ipairs');
|
||||
return mm ? mm(t).slice(0, 3) : [ipairsIterator, t, 0];
|
||||
}
|
||||
|
||||
|
||||
// TODO load(func [, chunkname])
|
||||
// TODO loadfile([filename])
|
||||
|
||||
|
||||
export function loadstring(str, chunkname) {
|
||||
str = coerceArgToString(str, 'loadstring', 1);
|
||||
let parser = global.starlight.parser;
|
||||
|
||||
if (!parser) {
|
||||
throw new Error('Starlight parser not found in call to loadstring(). The parser is required to execute Lua strings at runtime.');
|
||||
}
|
||||
|
||||
try {
|
||||
return parser.parse(str);
|
||||
} catch (e) {
|
||||
return [undefined, e.message];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function next(table, index) {
|
||||
table = coerceArgToTable(table, 'next', 1);
|
||||
|
||||
// SLOOOOOOOW...
|
||||
let found = (index === void 0),
|
||||
key, value,
|
||||
i, l;
|
||||
|
||||
if (found || (typeof index == 'number' && index > 0 && index == index >> 0)) {
|
||||
let numValues = table.numValues;
|
||||
|
||||
if ('keys' in Object) {
|
||||
// Use Object.keys, if available.
|
||||
let keys = Object['keys'](numValues);
|
||||
|
||||
if (found) {
|
||||
// First pass
|
||||
i = 1;
|
||||
|
||||
} else if (i = keys.indexOf('' + index) + 1) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
while ((key = keys[i]) !== void 0 && (value = numValues[key]) === void 0) i++;
|
||||
if (value !== void 0) return [key >>= 0, value];
|
||||
}
|
||||
|
||||
} else {
|
||||
// Else use for-in (faster than for loop on tables with large holes)
|
||||
|
||||
for (l in numValues) {
|
||||
i = l >> 0;
|
||||
|
||||
if (!found) {
|
||||
if (i === index) found = true;
|
||||
|
||||
} else if (numValues[i] !== void 0) {
|
||||
return [i, numValues[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i in table.strValues) {
|
||||
if (table.strValues.hasOwnProperty(i)) {
|
||||
if (!found) {
|
||||
if (i == index) found = true;
|
||||
|
||||
} else if (table.strValues[i] !== void 0) {
|
||||
return [i, table.strValues[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i in table.keys) {
|
||||
if (table.keys.hasOwnProperty(i)) {
|
||||
let key = table.keys[i];
|
||||
|
||||
if (!found) {
|
||||
if (key === index) found = true;
|
||||
|
||||
} else if (table.values[i] !== void 0) {
|
||||
return [key, table.values[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
export function pairs(table) {
|
||||
table = coerceArgToTable(table, 'pairs', 1);
|
||||
const mt = getmetatable(table);
|
||||
const mm = mt && mt.get('__pairs');
|
||||
return mm ? mm(table).slice(0, 3) : [next, table];
|
||||
}
|
||||
|
||||
|
||||
export function pcall(func, ...args) {
|
||||
let result;
|
||||
|
||||
try {
|
||||
if (typeof func == 'function') {
|
||||
result = func(...args);
|
||||
} else {
|
||||
throw new LuaError('Attempt to call non-function');
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
return [false, e && e.toString()];
|
||||
}
|
||||
|
||||
result = [].concat(result);
|
||||
return [true, ...result];
|
||||
}
|
||||
|
||||
|
||||
export function print(...args) {
|
||||
let output = args.map(arg => tostring(arg)).join('\t');
|
||||
stdout.writeln(output);
|
||||
}
|
||||
|
||||
|
||||
export function rawequal(v1, v2) {
|
||||
return v1 === v2;
|
||||
}
|
||||
|
||||
|
||||
export function rawget(table, index) {
|
||||
table = coerceArgToTable(table, 'rawget', 1);
|
||||
return table.rawget(index);
|
||||
}
|
||||
|
||||
|
||||
export function rawset(table, index, value) {
|
||||
table = coerceArgToTable(table, 'rawset', 1);
|
||||
if (index === void 0) throw new LuaError('table index is nil');
|
||||
|
||||
table.rawset(index, value);
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
export function _require(modname) {
|
||||
modname = coerceArgToString(modname, 'require', 1);
|
||||
modname = modname.replace(/\//g, '.');
|
||||
|
||||
let [preload, loaded] = getPackageMethods();
|
||||
let mod = loaded.rawget(modname);
|
||||
|
||||
if (mod) {
|
||||
return mod;
|
||||
}
|
||||
|
||||
let modinit = preload.rawget(modname);
|
||||
|
||||
if (modinit === void 0) {
|
||||
throw new LuaError(`module '${modname}' not found:\n\tno field package.preload['${modname}']`);
|
||||
}
|
||||
|
||||
let modResult = modinit(modname);
|
||||
mod = (modResult instanceof Array) ? modResult[0] : modResult;
|
||||
|
||||
loaded.rawset(modname, mod !== void 0 ? mod : true);
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
||||
export function select(index, ...args) {
|
||||
if (index === '#') {
|
||||
return args.length;
|
||||
|
||||
} else if (index = parseInt(index, 10)) {
|
||||
return args.slice(index - 1);
|
||||
|
||||
} else {
|
||||
let typ = type(index);
|
||||
throw new LuaError(`bad argument #1 to 'select' (number expected, got ${typ})`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO setfenv(f, table)
|
||||
|
||||
|
||||
export function setmetatable(table, metatable) {
|
||||
table = coerceArgToTable(table, 'setmetatable', 1);
|
||||
if (metatable !== void 0) {
|
||||
metatable = coerceArgToTable(metatable, 'setmetatable', 2);
|
||||
}
|
||||
|
||||
let mt;
|
||||
if (
|
||||
(mt = table.metatable)
|
||||
&& mt.rawget('__metatable')
|
||||
) {
|
||||
throw new LuaError('cannot change a protected metatable');
|
||||
}
|
||||
|
||||
table.metatable = metatable;
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
export function tonumber(e, base = 10) {
|
||||
base = coerceArgToNumber(base, 'tonumber', 2);
|
||||
|
||||
if (e === '') return;
|
||||
|
||||
if (base < 2 || base > 36) {
|
||||
throw new LuaError(`bad argument #2 to 'tonumber' (base out of range)`);
|
||||
}
|
||||
|
||||
if (base == 10 && (e === Infinity || e === -Infinity || (typeof e == 'number' && global.isNaN(e)))) {
|
||||
return e;
|
||||
}
|
||||
|
||||
if (base != 10 && e === void 0) {
|
||||
throw new LuaError("bad argument #1 to 'tonumber' (string expected, got nil)");
|
||||
}
|
||||
|
||||
e = `${e}`.trim();
|
||||
|
||||
// If using base 10, use normal coercion.
|
||||
if (base === 10) {
|
||||
return coerceToNumber(e);
|
||||
}
|
||||
|
||||
e = coerceToString(e);
|
||||
|
||||
// If using base 16, ingore any "0x" prefix
|
||||
let match;
|
||||
if (
|
||||
base === 16
|
||||
&& (match = e.match(/^(\-)?0[xX](.+)$/))
|
||||
) {
|
||||
e = `${match[1] || ''}${match[2]}`;
|
||||
}
|
||||
|
||||
let pattern = new RegExp('^[' + CHARS.substr(0, base) + ']*$', 'gi');
|
||||
|
||||
if (!pattern.test(e)) return; // Invalid
|
||||
return parseInt(e, base);
|
||||
}
|
||||
|
||||
|
||||
export function tostring(e) {
|
||||
let mt, mm;
|
||||
|
||||
if (
|
||||
e !== void 0
|
||||
&& e instanceof T
|
||||
&& (mt = e.metatable)
|
||||
&& (mm = mt.rawget('__tostring'))
|
||||
) {
|
||||
return mm.call(mm, e);
|
||||
}
|
||||
|
||||
if (e instanceof T) {
|
||||
return e.toString();
|
||||
} else if (e instanceof Function) {
|
||||
return e.hasOwnProperty('toString')? `${e}` : 'function: [host code]';
|
||||
}
|
||||
|
||||
return coerceToString(e);
|
||||
}
|
||||
|
||||
|
||||
export function type(v) {
|
||||
let t = typeof v;
|
||||
|
||||
switch (t) {
|
||||
case 'undefined':
|
||||
return 'nil';
|
||||
|
||||
case 'number':
|
||||
case 'string':
|
||||
case 'boolean':
|
||||
case 'function':
|
||||
return t;
|
||||
|
||||
case 'object':
|
||||
if (v.constructor === T) return 'table';
|
||||
if (v && v instanceof Function) return 'function';
|
||||
|
||||
return 'userdata';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function unpack(table, i = 1, j) {
|
||||
table = coerceArgToTable(table, 'unpack', 1);
|
||||
i = coerceArgToNumber(i, 'unpack', 2);
|
||||
|
||||
if (j === void 0) {
|
||||
j = getn(table);
|
||||
} else {
|
||||
j = coerceArgToNumber(j, 'unpack', 3);
|
||||
}
|
||||
|
||||
return table.numValues.slice(i, j + 1);
|
||||
}
|
||||
|
||||
|
||||
export function xpcall(func, err) {
|
||||
let result, success, invalid;
|
||||
|
||||
try {
|
||||
if (typeof func === 'function') {
|
||||
result = func();
|
||||
} else {
|
||||
invalid = true;
|
||||
}
|
||||
success = true;
|
||||
|
||||
} catch (e) {
|
||||
result = err(void 0, true)[0];
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (invalid) throw new LuaError('Attempt to call non-function');
|
||||
|
||||
if (!(result && result instanceof Array)) result = [result];
|
||||
result.unshift(success);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const _G = new T({
|
||||
_VERSION,
|
||||
assert,
|
||||
error,
|
||||
getmetatable,
|
||||
ipairs,
|
||||
loadstring,
|
||||
next,
|
||||
pairs,
|
||||
pcall,
|
||||
print,
|
||||
rawequal,
|
||||
rawget,
|
||||
rawset,
|
||||
require: _require,
|
||||
select,
|
||||
setmetatable,
|
||||
tonumber,
|
||||
tostring,
|
||||
type,
|
||||
unpack,
|
||||
xpcall,
|
||||
});
|
||||
|
||||
|
||||
_G.rawset('_G', _G);
|
||||
export default _G;
|
||||
@ -0,0 +1,253 @@
|
||||
import { default as T } from '../Table';
|
||||
import { coerceArgToNumber } from '../utils';
|
||||
|
||||
|
||||
const RANDOM_MULTIPLIER = 16807;
|
||||
const RANDOM_MODULUS = 2147483647;
|
||||
|
||||
let randomSeed = 1;
|
||||
|
||||
|
||||
function getRandom () {
|
||||
randomSeed = (RANDOM_MULTIPLIER * randomSeed) % RANDOM_MODULUS;
|
||||
return randomSeed / RANDOM_MODULUS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function abs(x) {
|
||||
x = coerceArgToNumber(x, 'abs', 1);
|
||||
return Math.abs(x);
|
||||
}
|
||||
|
||||
|
||||
export function acos(x) {
|
||||
x = coerceArgToNumber(x, 'acos', 1);
|
||||
return Math.acos(x);
|
||||
}
|
||||
|
||||
|
||||
export function asin(x) {
|
||||
x = coerceArgToNumber(x, 'asin', 1);
|
||||
return Math.asin(x);
|
||||
}
|
||||
|
||||
|
||||
export function atan(x) {
|
||||
x = coerceArgToNumber(x, 'atan', 1);
|
||||
return Math.atan(x);
|
||||
}
|
||||
|
||||
|
||||
export function atan2(y, x) {
|
||||
y = coerceArgToNumber(y, 'atan2', 1);
|
||||
x = coerceArgToNumber(x, 'atan2', 2);
|
||||
return Math.atan2(y, x);
|
||||
}
|
||||
|
||||
|
||||
export function ceil(x) {
|
||||
x = coerceArgToNumber(x, 'ceil', 1);
|
||||
return Math.ceil(x);
|
||||
}
|
||||
|
||||
|
||||
export function cos(x) {
|
||||
x = coerceArgToNumber(x, 'cos', 1);
|
||||
return Math.cos(x);
|
||||
}
|
||||
|
||||
|
||||
export function cosh(x) {
|
||||
x = coerceArgToNumber(x, 'cosh', 1);
|
||||
return (exp(x) + exp(-x)) / 2;
|
||||
}
|
||||
|
||||
|
||||
export function deg(x) {
|
||||
x = coerceArgToNumber(x, 'deg', 1);
|
||||
return x * 180 / Math.PI;
|
||||
}
|
||||
|
||||
|
||||
export function exp(x) {
|
||||
x = coerceArgToNumber(x, 'exp', 1);
|
||||
return Math.exp(x);
|
||||
}
|
||||
|
||||
|
||||
export function floor(x) {
|
||||
x = coerceArgToNumber(x, 'floor', 1);
|
||||
return Math.floor(x);
|
||||
}
|
||||
|
||||
|
||||
export function fmod(x, y) {
|
||||
x = coerceArgToNumber(x, 'fmod', 1);
|
||||
y = coerceArgToNumber(y, 'fmod', 2);
|
||||
return x % y;
|
||||
}
|
||||
|
||||
|
||||
export function frexp(x) {
|
||||
x = coerceArgToNumber(x, 'frexp', 1);
|
||||
|
||||
if (x === 0) {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
let delta = x > 0? 1 : -1;
|
||||
x *= delta;
|
||||
|
||||
let exponent = Math.floor(Math.log(x) / Math.log(2)) + 1;
|
||||
let mantissa = x / Math.pow(2, exponent);
|
||||
|
||||
return [mantissa * delta, exponent];
|
||||
}
|
||||
|
||||
|
||||
export const huge = Infinity;
|
||||
|
||||
|
||||
export function ldexp(m, e) {
|
||||
m = coerceArgToNumber(m, 'ldexp', 1);
|
||||
e = coerceArgToNumber(e, 'ldexp', 2);
|
||||
return m * Math.pow(2, e);
|
||||
}
|
||||
|
||||
|
||||
export function log(x, base) {
|
||||
x = coerceArgToNumber(x, 'log', 1);
|
||||
if (base === void 0) {
|
||||
return Math.log(x);
|
||||
} else {
|
||||
y = coerceArgToNumber(y, 'log', 2);
|
||||
return Math.log(x) / Math.log(base);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function log10(x) {
|
||||
x = coerceArgToNumber(x, 'log10', 1);
|
||||
// v5.2: shine.warn ('math.log10 is deprecated. Use math.log with 10 as its second argument instead.');
|
||||
return Math.log(x) / Math.log(10);
|
||||
}
|
||||
|
||||
|
||||
export function max(...args) {
|
||||
return Math.max(...args);
|
||||
}
|
||||
|
||||
|
||||
export function min(...args) {
|
||||
return Math.min(...args);
|
||||
}
|
||||
|
||||
|
||||
export function modf(x) {
|
||||
x = coerceArgToNumber(x, 'modf', 1);
|
||||
let intValue = Math.floor(x);
|
||||
let mantissa = x - intValue;
|
||||
return [intValue, mantissa];
|
||||
}
|
||||
|
||||
|
||||
export const pi = Math.PI;
|
||||
|
||||
|
||||
export function pow(x, y) {
|
||||
x = coerceArgToNumber(x, 'pow', 1);
|
||||
y = coercArgToNumber(y, 'pow', 2);
|
||||
return Math.pow(x, y);
|
||||
}
|
||||
|
||||
|
||||
export function rad(x) {
|
||||
x = coerceArgToNumber(x, 'rad', 1);
|
||||
return (Math.PI / 180) * x;
|
||||
}
|
||||
|
||||
|
||||
export function random(min, max) {
|
||||
if (min === void 0 && max === void 0) return getRandom();
|
||||
min = coerceArgToNumber(min, 'random', 1);
|
||||
|
||||
if (max === void 0) {
|
||||
max = min;
|
||||
min = 1;
|
||||
} else {
|
||||
max = coerceArgToNumber(max, 'random', 2);
|
||||
}
|
||||
if (min > max) throw new shine.Error("bad argument #2 to 'random' (interval is empty)");
|
||||
return Math.floor(getRandom() * (max - min + 1) + min);
|
||||
}
|
||||
|
||||
|
||||
export function randomseed(x) {
|
||||
x = coerceArgToNumber(x, 'randomseed', 1);
|
||||
randomSeed = x;
|
||||
}
|
||||
|
||||
|
||||
export function sin(x) {
|
||||
x = coerceArgToNumber(x, 'sin', 1);
|
||||
return Math.sin(x);
|
||||
}
|
||||
|
||||
|
||||
export function sinh(x) {
|
||||
x = coerceArgToNumber(x, 'sinh', 1);
|
||||
return (exp(x) - exp(-x)) / 2;
|
||||
}
|
||||
|
||||
|
||||
export function sqrt(x) {
|
||||
x = coerceArgToNumber(x, 'sqrt', 1);
|
||||
return Math.sqrt(x);
|
||||
}
|
||||
|
||||
|
||||
export function tan(x) {
|
||||
x = coerceArgToNumber(x, 'tan', 1);
|
||||
return Math.tan(x);
|
||||
}
|
||||
|
||||
|
||||
export function tanh(x) {
|
||||
x = coerceArgToNumber(x, 'tanh', 1);
|
||||
return (exp(x) - exp(-x))/(exp(x) + exp(-x));
|
||||
}
|
||||
|
||||
|
||||
export default new T({
|
||||
abs,
|
||||
acos,
|
||||
asin,
|
||||
atan,
|
||||
atan2,
|
||||
ceil,
|
||||
cos,
|
||||
cosh,
|
||||
deg,
|
||||
exp,
|
||||
floor,
|
||||
fmod,
|
||||
frexp,
|
||||
huge,
|
||||
ldexp,
|
||||
log,
|
||||
log10,
|
||||
max,
|
||||
min,
|
||||
modf,
|
||||
pi,
|
||||
pow,
|
||||
rad,
|
||||
random,
|
||||
randomseed,
|
||||
sin,
|
||||
sinh,
|
||||
sqrt,
|
||||
tan,
|
||||
tanh
|
||||
});
|
||||
@ -0,0 +1,115 @@
|
||||
import { default as T } from '../Table';
|
||||
|
||||
|
||||
const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
const DATE_FORMAT_HANDLERS = {
|
||||
'%a': (d, utc) => DAYS[d['get' + (utc? 'UTC' : '') + 'Day']()].substr(0, 3),
|
||||
'%A': (d, utc) => DAYS[d['get' + (utc? 'UTC' : '') + 'Day']()],
|
||||
'%b': (d, utc) => MONTHS[d['get' + (utc? 'UTC' : '') + 'Month']()].substr(0, 3),
|
||||
'%B': (d, utc) => MONTHS[d['get' + (utc? 'UTC' : '') + 'Month']()],
|
||||
'%c': (d, utc) => d['to' + (utc? 'UTC' : '') + 'LocaleString'](),
|
||||
'%d': (d, utc) => ('0' + d['get' + (utc? 'UTC' : '') + 'Date']()).substr(-2),
|
||||
'%H': (d, utc) => ('0' + d['get' + (utc? 'UTC' : '') + 'Hours']()).substr(-2),
|
||||
'%I': (d, utc) => ('0' + ((d['get' + (utc? 'UTC' : '') + 'Hours']() + 11) % 12 + 1)).substr(-2),
|
||||
'%j': (d, utc) => {
|
||||
let result = d['get' + (utc? 'UTC' : '') + 'Date']();
|
||||
let m = d['get' + (utc? 'UTC' : '') + 'Month']();
|
||||
|
||||
for (let i = 0; i < m; i++) {
|
||||
result += DAYS_IN_MONTH[i];
|
||||
}
|
||||
|
||||
if (m > 1 && d['get' + (utc? 'UTC' : '') + 'FullYear']() % 4 === 0) {
|
||||
result +=1;
|
||||
}
|
||||
|
||||
return ('00' + result).substr(-3);
|
||||
},
|
||||
'%m': (d, utc) => ('0' + (d['get' + (utc? 'UTC' : '') + 'Month']() + 1)).substr(-2),
|
||||
'%M': (d, utc) => ('0' + d['get' + (utc? 'UTC' : '') + 'Minutes']()).substr(-2),
|
||||
'%p': (d, utc) => (d['get' + (utc? 'UTC' : '') + 'Hours']() < 12)? 'AM' : 'PM',
|
||||
'%S': (d, utc) => ('0' + d['get' + (utc? 'UTC' : '') + 'Seconds']()).substr(-2),
|
||||
'%U': (d, utc) => getWeekOfYear(d, 0, utc),
|
||||
'%w': (d, utc) => '' + (d['get' + (utc? 'UTC' : '') + 'Day']()),
|
||||
'%W': (d, utc) => getWeekOfYear(d, 1, utc),
|
||||
'%x': (d, utc) => DATE_FORMAT_HANDLERS['%m'](d, utc) + '/' + DATE_FORMAT_HANDLERS['%d'](d, utc) + '/' + DATE_FORMAT_HANDLERS['%y'](d, utc),
|
||||
'%X': (d, utc) => DATE_FORMAT_HANDLERS['%H'](d, utc) + ':' + DATE_FORMAT_HANDLERS['%M'](d, utc) + ':' + DATE_FORMAT_HANDLERS['%S'](d, utc),
|
||||
'%y': (d, utc) => DATE_FORMAT_HANDLERS['%Y'](d, utc).substr (-2),
|
||||
'%Y': (d, utc) => '' + d['get' + (utc? 'UTC' : '') + 'FullYear'](),
|
||||
'%Z': (d, utc) => { let m; return (utc && 'UTC') || ((m = d.toString().match(/[A-Z][A-Z][A-Z]/)) && m[0]); },
|
||||
'%%': () => '%'
|
||||
}
|
||||
|
||||
|
||||
function isDST(date) {
|
||||
let year = date.getFullYear();
|
||||
let jan = new Date(year, 0);
|
||||
|
||||
// ASSUMPTION: If the time offset of the date is the same as it would be in January of the same year, DST is not in effect.
|
||||
return (date.getTimezoneOffset() !== jan.getTimezoneOffset());
|
||||
}
|
||||
|
||||
|
||||
function getWeekOfYear (d, firstDay, utc) {
|
||||
let dayOfYear = parseInt(DATE_FORMAT_HANDLERS['%j'](d), 10);
|
||||
let jan1 = new Date(d.getFullYear (), 0, 1, 12);
|
||||
let offset = (8 - jan1['get' + (utc? 'UTC' : '') + 'Day']() + firstDay) % 7;
|
||||
|
||||
return ('0' + (Math.floor((dayOfYear - offset) / 7) + 1)).substr(-2);
|
||||
}
|
||||
|
||||
|
||||
export function date(format = '%c', time) {
|
||||
let utc,
|
||||
date = new Date();
|
||||
|
||||
if (time) {
|
||||
date.setTime(time * 1000);
|
||||
}
|
||||
|
||||
if (format.substr(0, 1) === '!') {
|
||||
format = format.substr(1);
|
||||
utc = true;
|
||||
}
|
||||
|
||||
if (format === '*t') {
|
||||
return new T ({
|
||||
year: parseInt(DATE_FORMAT_HANDLERS['%Y'](date, utc), 10),
|
||||
month: parseInt(DATE_FORMAT_HANDLERS['%m'](date, utc), 10),
|
||||
day: parseInt(DATE_FORMAT_HANDLERS['%d'](date, utc), 10),
|
||||
hour: parseInt(DATE_FORMAT_HANDLERS['%H'](date, utc), 10),
|
||||
min: parseInt(DATE_FORMAT_HANDLERS['%M'](date, utc), 10),
|
||||
sec: parseInt(DATE_FORMAT_HANDLERS['%S'](date, utc), 10),
|
||||
wday: parseInt(DATE_FORMAT_HANDLERS['%w'](date, utc), 10) + 1,
|
||||
yday: parseInt(DATE_FORMAT_HANDLERS['%j'](date, utc), 10),
|
||||
isdst: isDST(date)
|
||||
});
|
||||
}
|
||||
|
||||
for (let i in DATE_FORMAT_HANDLERS) {
|
||||
if (DATE_FORMAT_HANDLERS.hasOwnProperty(i) && format.indexOf(i) >= 0) {
|
||||
format = format.replace(i, DATE_FORMAT_HANDLERS[i](date, utc));
|
||||
}
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
export function exit(code = 0) {
|
||||
var process = global.process;
|
||||
if (process && process.exit) {
|
||||
process.exit(code);
|
||||
} else {
|
||||
throw new Error(`Exit with code: ${code}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new T({
|
||||
date,
|
||||
exit
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { default as T } from '../Table';
|
||||
|
||||
|
||||
export default new T({
|
||||
preload: new T(),
|
||||
loaded: new T()
|
||||
});
|
||||
@ -0,0 +1,319 @@
|
||||
import { default as T } from '../Table';
|
||||
import { default as LuaError } from '../LuaError';
|
||||
import { tostring } from './string';
|
||||
import printf from 'printf';
|
||||
|
||||
import {
|
||||
coerceToNumber,
|
||||
coerceToString,
|
||||
coerceToBoolean,
|
||||
coerceArgToNumber,
|
||||
coerceArgToString,
|
||||
coerceArgToFunction
|
||||
} from '../utils';
|
||||
|
||||
|
||||
const ROSETTA_STONE = {
|
||||
'([^a-zA-Z0-9%(])-': '$1*?',
|
||||
'([^%])-([^a-zA-Z0-9?])': '$1*?$2',
|
||||
'(.)-$': '$1*?',
|
||||
'%a': '[a-zA-Z]',
|
||||
'%A': '[^a-zA-Z]',
|
||||
'%c': '[\x00-\x1f]',
|
||||
'%C': '[^\x00-\x1f]',
|
||||
'%d': '\\d',
|
||||
'%D': '[^\d]',
|
||||
'%l': '[a-z]',
|
||||
'%L': '[^a-z]',
|
||||
'%p': '[\.\,\"\'\?\!\;\:\#\$\%\&\(\)\*\+\-\/\<\>\=\@\\[\\]\\\\^\_\{\}\|\~]',
|
||||
'%P': '[^\.\,\"\'\?\!\;\:\#\$\%\&\(\)\*\+\-\/\<\>\=\@\\[\\]\\\\^\_\{\}\|\~]',
|
||||
'%s': '[ \\t\\n\\f\\v\\r]',
|
||||
'%S': '[^ \t\n\f\v\r]',
|
||||
'%u': '[A-Z]',
|
||||
'%U': '[^A-Z]',
|
||||
'%w': '[a-zA-Z0-9]',
|
||||
'%W': '[^a-zA-Z0-9]',
|
||||
'%x': '[a-fA-F0-9]',
|
||||
'%X': '[^a-fA-F0-9]',
|
||||
'%([^a-zA-Z])': '\\$1'
|
||||
};
|
||||
|
||||
|
||||
function translatePattern (pattern) {
|
||||
// TODO Add support for balanced character matching (not sure this is easily achieveable).
|
||||
pattern = '' + pattern;
|
||||
|
||||
// Replace single backslash with double backslashes
|
||||
pattern = pattern.replace(new RegExp('\\\\', 'g'), '\\\\');
|
||||
|
||||
for (let i in ROSETTA_STONE) {
|
||||
if (ROSETTA_STONE.hasOwnProperty(i)) {
|
||||
pattern = pattern.replace(new RegExp(i, 'g'), ROSETTA_STONE[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let l = pattern.length;
|
||||
let n = 0;
|
||||
|
||||
for (let i = 0; i < l; i++) {
|
||||
const character = pattern.substr(i, 1);
|
||||
if (i && pattern.substr(i - 1, 1) == '\\') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let addSlash = false;
|
||||
|
||||
if (character == '[') {
|
||||
if (n) addSlash = true;
|
||||
n++;
|
||||
|
||||
} else if (character == ']' && pattern.substr(i - 1, 1) !== '\\') {
|
||||
n--;
|
||||
if (n) addSlash = true;
|
||||
}
|
||||
|
||||
if (addSlash) {
|
||||
pattern = pattern.substr(0, i) + pattern.substr(i++ + 1);
|
||||
l++;
|
||||
}
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function byte(s, i = 1, j) {
|
||||
s = coerceArgToString(s, 'byte', 1);
|
||||
i = coerceArgToNumber(i, 'byte', 2);
|
||||
if (j === void 0) {
|
||||
j = i;
|
||||
} else {
|
||||
j = coerceArgToNumber(j, 'byte', 3);
|
||||
}
|
||||
|
||||
return s.substring(i - 1, j).split('').map(c => c.charCodeAt(0));
|
||||
}
|
||||
|
||||
|
||||
export function char(...bytes) {
|
||||
return bytes.map((b, i) => {
|
||||
b = coerceArgToNumber(b, 'char', i);
|
||||
return String.fromCharCode(b);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
|
||||
export function dump(func) {
|
||||
func = coerceArgToFunction(func, 'dump', 1);
|
||||
throw new LuaError('string.dump() is not supported');
|
||||
}
|
||||
|
||||
|
||||
export function find(s, pattern, init = 1, plain = false) {
|
||||
s = coerceArgToString(s, 'find', 1);
|
||||
pattern = coerceArgToString(pattern, 'find', 2);
|
||||
init = coerceArgToNumber(init, 'find', 3);
|
||||
plain = coerceToBoolean(plain);
|
||||
|
||||
// Regex
|
||||
if (!plain) {
|
||||
pattern = translatePattern(pattern);
|
||||
let reg = new RegExp(pattern);
|
||||
let index = s.substr(init - 1).search(reg);
|
||||
|
||||
if (index < 0) return;
|
||||
|
||||
let match = s.substr(init - 1).match(reg);
|
||||
let result = [index + init, index + init + match[0].length - 1];
|
||||
|
||||
match.shift();
|
||||
return result.concat(match);
|
||||
}
|
||||
|
||||
// Plain
|
||||
let index = s.indexOf(pattern, init - 1);
|
||||
return (index === -1)? void 0 : [index + 1, index + pattern.length];
|
||||
}
|
||||
|
||||
|
||||
// TODO string.format (formatstring, ···)
|
||||
export function format(formatstring, ...args) {
|
||||
return printf(formatstring, ...args);
|
||||
}
|
||||
|
||||
|
||||
export function gmatch(s, pattern) {
|
||||
s = coerceArgToString(s, 'gmatch', 1);
|
||||
pattern = coerceArgToString(pattern, 'gmatch', 2);
|
||||
pattern = translatePattern(pattern);
|
||||
|
||||
let reg = new RegExp(pattern, 'g'),
|
||||
matches = s.match(reg);
|
||||
|
||||
return () => {
|
||||
let match = matches.shift(),
|
||||
groups = new RegExp(pattern).exec(match);
|
||||
|
||||
if (match === void 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
groups.shift();
|
||||
return groups.length? groups : match;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function gsub(s, pattern, repl, n = Infinity) {
|
||||
s = coerceArgToString(s, 'gsub', 1);
|
||||
pattern = coerceArgToString(pattern, 'gsub', 2);
|
||||
n = coerceArgToNumber(n, 'gsub', 3);
|
||||
|
||||
pattern = translatePattern('' + pattern);
|
||||
let replIsFunction = (typeof repl == 'function');
|
||||
|
||||
let count = 0,
|
||||
result = '',
|
||||
str,
|
||||
prefix,
|
||||
match,
|
||||
lastMatch;
|
||||
|
||||
while (
|
||||
count < n
|
||||
&& s
|
||||
&& (match = s.match(pattern))
|
||||
) {
|
||||
if (replIsFunction) {
|
||||
str = repl(match[0]);
|
||||
if (str instanceof Array) str = str[0];
|
||||
if (str === void 0) str = match[0];
|
||||
|
||||
} else if (repl instanceof T) {
|
||||
str = repl.get(match[0]);
|
||||
|
||||
} else if (typeof repl == 'object') {
|
||||
str = repl[match];
|
||||
|
||||
} else {
|
||||
str = `${repl}`.replace(/%([0-9])/g, (m, i) => match[i]);
|
||||
|
||||
}
|
||||
|
||||
if (match[0].length === 0) {
|
||||
if (lastMatch === void 0) {
|
||||
prefix = '';
|
||||
} else {
|
||||
prefix = s.substr(0, 1);
|
||||
}
|
||||
|
||||
} else {
|
||||
prefix = s.substr(0, match.index);
|
||||
}
|
||||
|
||||
lastMatch = match[0];
|
||||
result += `${prefix}${str}`;
|
||||
s = s.substr(`${prefix}${lastMatch}`.length);
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return [`${result}${s}`, count];
|
||||
}
|
||||
|
||||
|
||||
export function len(s) {
|
||||
s = coerceArgToString(s, 'len', 1);
|
||||
return s.length;
|
||||
}
|
||||
|
||||
|
||||
export function lower (s) {
|
||||
s = coerceArgToString(s, 'lower', 1);
|
||||
return s.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
export function match(s, pattern, init = 0) {
|
||||
s = coerceArgToString(s, 'match', 1);
|
||||
pattern = coerceArgToString(pattern, 'match', 2);
|
||||
init = coerceArgToNumber(init, 'match', 3);
|
||||
|
||||
s = s.substr(init);
|
||||
let matches = s.match(new RegExp(translatePattern (pattern)));
|
||||
|
||||
if (!matches) {
|
||||
return;
|
||||
} else if (!matches[1]) {
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
matches.shift();
|
||||
return matches;
|
||||
}
|
||||
|
||||
|
||||
export function rep(s, n) {
|
||||
s = coerceArgToString(s, 'rep', 1);
|
||||
n = coerceArgToNumber(n, 'rep', 2);
|
||||
return Array(n + 1).join(s);
|
||||
}
|
||||
|
||||
|
||||
export function reverse(s) {
|
||||
s = coerceArgToString(s, 'reverse', 1);
|
||||
return Array.prototype.map.call(s, l => l).reverse().join('');
|
||||
}
|
||||
|
||||
|
||||
export function sub(s, i = 1, j) {
|
||||
s = coerceArgToString(s, 'sub', 1);
|
||||
i = coerceArgToNumber(i, 'sub', 2);
|
||||
|
||||
if (j === void 0) {
|
||||
s.length;
|
||||
} else {
|
||||
j = coerceArgToNumber(j, 'sub', 3);
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
i = i - 1;
|
||||
} else if (i < 0) {
|
||||
i = s.length + i;
|
||||
}
|
||||
|
||||
if (j < 0) {
|
||||
j = s.length + j + 1;
|
||||
}
|
||||
|
||||
return s.substring(i, j);
|
||||
}
|
||||
|
||||
|
||||
export function upper(s) {
|
||||
s = coerceArgToString(s, 'upper', 1);
|
||||
return s.toUpperCase();
|
||||
}
|
||||
|
||||
|
||||
|
||||
const string = new T({
|
||||
byte,
|
||||
char,
|
||||
dump,
|
||||
find,
|
||||
format,
|
||||
gmatch,
|
||||
gsub,
|
||||
len,
|
||||
lower,
|
||||
match,
|
||||
rep,
|
||||
reverse,
|
||||
sub,
|
||||
upper
|
||||
});
|
||||
|
||||
export default string;
|
||||
export const metatable = new T({ __index: string });
|
||||
@ -0,0 +1,136 @@
|
||||
import { default as T } from '../Table';
|
||||
import { default as LuaError } from '../LuaError';
|
||||
import {
|
||||
coerceToNumber,
|
||||
coerceToBoolean,
|
||||
coerceArgToNumber,
|
||||
coerceArgToString,
|
||||
coerceArgToTable,
|
||||
coerceArgToFunction
|
||||
} from '../utils';
|
||||
|
||||
|
||||
|
||||
export function concat(table, sep = '', i = 1, j) {
|
||||
table = coerceArgToTable(table, 'concat', 1);
|
||||
sep = coerceArgToString(sep, 'concat', 2);
|
||||
i = coerceArgToNumber(i, 'concat', 3);
|
||||
|
||||
if (j === void 0) {
|
||||
j = maxn(table);
|
||||
} else {
|
||||
j = coerceArgToNumber(j, 'concat', 4);
|
||||
}
|
||||
|
||||
return [].concat(table.numValues).splice(i, j - i + 1).join(sep);
|
||||
}
|
||||
|
||||
|
||||
export function getn(table) {
|
||||
table = coerceArgToTable(table, 'getn', 1);
|
||||
|
||||
let vals = table.numValues,
|
||||
keys = [],
|
||||
j = 0;
|
||||
|
||||
for (let i in vals) {
|
||||
if (vals.hasOwnProperty(i)) {
|
||||
keys[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (keys[j + 1]) j++;
|
||||
|
||||
// Following translated from ltable.c (http://www.lua.org/source/5.1/ltable.c.html)
|
||||
if (j > 0 && vals[j] === void 0) {
|
||||
/* there is a boundary in the array part: (binary) search for it */
|
||||
let i = 0;
|
||||
|
||||
while (j - i > 1) {
|
||||
let m = Math.floor((i + j) / 2);
|
||||
|
||||
if (vals[m] === void 0) {
|
||||
j = m;
|
||||
} else {
|
||||
i = m;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
|
||||
export function insert(table, index, obj) {
|
||||
table = coerceArgToTable(table, 'insert', 1);
|
||||
|
||||
if (obj === void 0) {
|
||||
obj = index;
|
||||
index = table.numValues.length;
|
||||
} else {
|
||||
index = coerceArgToNumber(index, 'insert', 2);
|
||||
}
|
||||
|
||||
table.numValues.splice(index, 0, void 0);
|
||||
table.set(index, obj);
|
||||
}
|
||||
|
||||
|
||||
export function maxn(table) {
|
||||
table = coerceArgToTable(table, 'maxn', 1);
|
||||
return table.numValues.length - 1;
|
||||
}
|
||||
|
||||
|
||||
export function remove(table, index) {
|
||||
table = coerceArgToTable(table, 'remove', 1);
|
||||
index = coerceArgToNumber(index, 'remove', 2);
|
||||
|
||||
let max = getn(table);
|
||||
let vals = table.numValues;
|
||||
|
||||
if (index > max) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index === void 0) {
|
||||
index = max;
|
||||
}
|
||||
|
||||
let result = vals.splice(index, 1);
|
||||
while (index < max && vals[index] === void 0) {
|
||||
delete vals[index++];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function sort(table, comp) {
|
||||
table = coerceArgToTable(table, 'sort', 1);
|
||||
|
||||
let sortFunc;
|
||||
let arr = table.numValues;
|
||||
|
||||
if (comp) {
|
||||
comp = coerceArgToFunction(comp, 'sort', 2);
|
||||
sortFunc = (a, b) => coerceToBoolean(comp(a, b)[0])? -1 : 1;
|
||||
} else {
|
||||
sortFunc = (a, b) => a < b? -1 : 1;
|
||||
}
|
||||
|
||||
arr.shift();
|
||||
arr.sort(sortFunc).unshift(void 0);
|
||||
}
|
||||
|
||||
|
||||
export default new T({
|
||||
concat,
|
||||
getn,
|
||||
insert,
|
||||
maxn,
|
||||
remove,
|
||||
sort
|
||||
});
|
||||
@ -0,0 +1,157 @@
|
||||
import { default as T } from './Table';
|
||||
import { coerceToNumber, coerceToBoolean, coerceToString } from './utils';
|
||||
import { getn } from './lib/table';
|
||||
import { default as LuaError } from './LuaError';
|
||||
|
||||
|
||||
function binaryArithmetic(left, right, metaMethodName, callback) {
|
||||
let mt, f;
|
||||
|
||||
if ((left && left instanceof T && (mt = left.metatable) && (f = mt.rawget(metaMethodName)))
|
||||
|| (right && right instanceof T && (mt = right.metatable) && (f = mt.rawget(metaMethodName)))) {
|
||||
return f(left, right)[0];
|
||||
}
|
||||
|
||||
if (typeof left !== 'number') {
|
||||
left = coerceToNumber(left, 'attempt to perform arithmetic on a %type value');
|
||||
}
|
||||
|
||||
if (typeof right !== 'number') {
|
||||
right = coerceToNumber(right, 'attempt to perform arithmetic on a %type value');
|
||||
}
|
||||
|
||||
return callback(left, right);
|
||||
}
|
||||
|
||||
|
||||
function binaryStringArithmetic(left, right, metaMethodName, callback) {
|
||||
if (typeof left == 'string' && typeof right == 'string') {
|
||||
return callback(left, right);
|
||||
}
|
||||
return binaryArithmetic(left, right, metaMethodName, callback);
|
||||
}
|
||||
|
||||
|
||||
function concat(left, right) {
|
||||
let mt, f;
|
||||
|
||||
if (
|
||||
(left && left instanceof T && (mt = left.metatable) && (f = mt.rawget('__concat')))
|
||||
|| (right && right instanceof T && (mt = right.metatable) && (f = mt.rawget('__concat')))
|
||||
) {
|
||||
return f(left, right)[0];
|
||||
} else {
|
||||
right = coerceToString(right, 'attempt to concatenate a %type value');
|
||||
left = coerceToString(left, 'attempt to concatenate a %type value');
|
||||
return `${left}${right}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function equal(left, right) {
|
||||
var mtl, mtr, f, result;
|
||||
|
||||
if (right !== left
|
||||
&& left && left instanceof T
|
||||
&& right && right instanceof T
|
||||
&& (mtl = left.metatable)
|
||||
&& (mtr = right.metatable)
|
||||
&& mtl === mtr
|
||||
&& (f = mtl.rawget('__eq'))
|
||||
) {
|
||||
return !!f(left, right)[0];
|
||||
}
|
||||
|
||||
return (left === right);
|
||||
}
|
||||
|
||||
|
||||
function mod(left, right) {
|
||||
if (
|
||||
right === 0
|
||||
|| right === -Infinity
|
||||
|| right === Infinity
|
||||
|| global.isNaN(left)
|
||||
|| global.isNaN(right)
|
||||
) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
let absRight = Math.abs(right);
|
||||
let result = Math.abs(left) % absRight;
|
||||
|
||||
if (left * right < 0) result = absRight - result;
|
||||
if (right < 0) result *= -1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function len(value) {
|
||||
let length, i;
|
||||
|
||||
if (value === undefined) throw new LuaError('attempt to get length of a nil value');
|
||||
if (value instanceof T) return getn(value);
|
||||
|
||||
if (typeof value == 'object') {
|
||||
let length = 0;
|
||||
for (let key in value) {
|
||||
if (value.hasOwnProperty(key)) {
|
||||
length++;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
return value.length;
|
||||
}
|
||||
|
||||
|
||||
function unaryMinus(value) {
|
||||
var mt, f, result;
|
||||
|
||||
if (value && value instanceof T && (mt = value.metatable) && (f = mt.rawget('__unm'))) {
|
||||
return f(value)[0];
|
||||
}
|
||||
|
||||
if (typeof value !== 'number') {
|
||||
value = coerceToNumber(value, 'attempt to perform arithmetic on a %type value');
|
||||
}
|
||||
|
||||
return -value;
|
||||
}
|
||||
|
||||
|
||||
const op = {
|
||||
concat,
|
||||
len,
|
||||
|
||||
eq: equal,
|
||||
unm: unaryMinus,
|
||||
bool: coerceToBoolean,
|
||||
|
||||
neq: (...args) => !equal(...args),
|
||||
not: (...args) => !coerceToBoolean(...args),
|
||||
|
||||
add: (left, right) => binaryArithmetic(left, right, '__add', (l, r) => l + r),
|
||||
sub: (left, right) => binaryArithmetic(left, right, '__sub', (l, r) => l - r),
|
||||
mul: (left, right) => binaryArithmetic(left, right, '__mul', (l, r) => l * r),
|
||||
div: (left, right) => {
|
||||
if (right === undefined) throw new LuaError('attempt to perform arithmetic on a nil value');
|
||||
return binaryArithmetic(left, right, '__div', (l, r) => l / r);
|
||||
},
|
||||
mod: (left, right) => binaryArithmetic(left, right, '__mod', mod),
|
||||
pow: (left, right) => binaryArithmetic(left, right, '__pow', Math.pow),
|
||||
lt: (left, right) => binaryStringArithmetic(left, right, '__lt', (l, r) => l < r),
|
||||
lte: (left, right) => binaryStringArithmetic(left, right, '__le', (l, r) => l <= r),
|
||||
|
||||
gt(left, right) {
|
||||
return !op.lte(left, right);
|
||||
},
|
||||
|
||||
gte(left, right) {
|
||||
return !op.lt(left, right);
|
||||
}
|
||||
};
|
||||
|
||||
export default op;
|
||||
@ -0,0 +1,173 @@
|
||||
import { type } from './lib/globals';
|
||||
import { default as LuaError } from './LuaError';
|
||||
import { default as T } from './Table';
|
||||
|
||||
|
||||
/**
|
||||
* Pattern to identify a float string value that can validly be converted to a number in Lua.
|
||||
* @type RegExp
|
||||
* @constant
|
||||
*/
|
||||
const FLOATING_POINT_PATTERN = /^[-+]?[0-9]*\.?([0-9]+([eE][-+]?[0-9]+)?)?$/;
|
||||
|
||||
|
||||
/**
|
||||
* Pattern to identify a hex string value that can validly be converted to a number in Lua.
|
||||
* @type RegExp
|
||||
* @constant
|
||||
*/
|
||||
const HEXIDECIMAL_CONSTANT_PATTERN = /^(\-)?0x([0-9a-fA-F]*)\.?([0-9a-fA-F]*)$/;
|
||||
|
||||
|
||||
function defaultWriteln (...args) {
|
||||
console.log(...args);
|
||||
}
|
||||
|
||||
|
||||
/********************
|
||||
* Stdout
|
||||
********************/
|
||||
|
||||
export const stdout = {
|
||||
writeln: (() => {
|
||||
let namespace, config, stdout;
|
||||
return (namespace = global.starlight)
|
||||
&& (config = namespace.config)
|
||||
&& (stdout = config.stdout)
|
||||
&& stdout.writeln
|
||||
|| defaultWriteln;
|
||||
})()
|
||||
};
|
||||
|
||||
|
||||
|
||||
/********************
|
||||
* Coercion
|
||||
********************/
|
||||
|
||||
/**
|
||||
* Thows an error with the type of a variable included in the message
|
||||
* @param {Object} val The value whise type is to be inspected.
|
||||
* @param {String} errorMessage The error message to throw.
|
||||
* @throws {LuaError}
|
||||
*/
|
||||
function throwCoerceError (val, errorMessage) {
|
||||
if (!errorMessage) return;
|
||||
errorMessage = ('' + errorMessage).replace(/\%type/gi, type(val));
|
||||
throw new LuaError(errorMessage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Coerces a value from its current type to a boolean in the same manner as Lua.
|
||||
* @param {Object} val The value to be converted.
|
||||
* @returns {Boolean} The converted value.
|
||||
*/
|
||||
export function coerceToBoolean(val) {
|
||||
return !(val === false || val === void 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Coerces a value from its current type to a number in the same manner as Lua.
|
||||
* @param {Object} val The value to be converted.
|
||||
* @param {String} [errorMessage] The error message to throw if the conversion fails.
|
||||
* @returns {Number} The converted value.
|
||||
*/
|
||||
export function coerceToNumber(val, errorMessage) {
|
||||
let n, match, mantissa;
|
||||
|
||||
switch (true) {
|
||||
case typeof val == 'number': return val;
|
||||
case val === void 0: return;
|
||||
case val === 'inf': return Infinity;
|
||||
case val === '-inf': return -Infinity;
|
||||
case val === 'nan': return NaN;
|
||||
|
||||
default:
|
||||
if (('' + val).match(FLOATING_POINT_PATTERN)) {
|
||||
n = parseFloat(val);
|
||||
|
||||
} else if (match = ('' + val).match(HEXIDECIMAL_CONSTANT_PATTERN)) {
|
||||
mantissa = match[3];
|
||||
|
||||
if ((n = match[2]) || mantissa) {
|
||||
n = parseInt(n, 16) || 0;
|
||||
if (mantissa) n += parseInt(mantissa, 16) / Math.pow(16, mantissa.length);
|
||||
if (match[1]) n *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (n === void 0) {
|
||||
throwCoerceError(val, errorMessage);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Coerces a value from its current type to a string in the same manner as Lua.
|
||||
* @param {Object} val The value to be converted.
|
||||
* @param {String} [errorMessage] The error message to throw if the conversion fails.
|
||||
* @returns {String} The converted value.
|
||||
*/
|
||||
export function coerceToString(val, errorMessage) {
|
||||
switch(true) {
|
||||
case typeof val == 'string':
|
||||
return val;
|
||||
|
||||
case val === void 0:
|
||||
case val === null:
|
||||
return 'nil';
|
||||
|
||||
case val === Infinity:
|
||||
return 'inf';
|
||||
|
||||
case val === -Infinity:
|
||||
return '-inf';
|
||||
|
||||
case typeof val == 'number':
|
||||
case typeof val == 'boolean':
|
||||
return global.isNaN(val)? 'nan' : `${val}`;
|
||||
|
||||
default:
|
||||
return throwCoerceError(val, errorMessage) || 'userdata';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function coerceArg(value, coerceFunc, typ, funcName, index) {
|
||||
return coerceFunc(value, `bad argument #${index} to '${funcName}' (${typ} expected, got %type)`);
|
||||
}
|
||||
|
||||
export function coerceArgToNumber(value, funcName, index) {
|
||||
return coerceArg(value, coerceToNumber, 'number', funcName, index);
|
||||
}
|
||||
|
||||
export function coerceArgToString(value, funcName, index) {
|
||||
return coerceArg(value, coerceToString, 'string', funcName, index);
|
||||
}
|
||||
|
||||
export function coerceArgToTable(value, funcName, index) {
|
||||
if (value instanceof T) {
|
||||
return value;
|
||||
} else {
|
||||
let typ = type(value);
|
||||
throw new LuaError(`bad argument #${index} to '${funcName}' (table expected, got ${typ})`);
|
||||
}
|
||||
}
|
||||
|
||||
export function coerceArgToFunction(value, funcName, index) {
|
||||
if (value instanceof Function) {
|
||||
return value;
|
||||
} else {
|
||||
let typ = type(value);
|
||||
throw new LuaError(`bad argument #${index} to '${funcName}' (function expected, got ${typ})`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
target: 'node',
|
||||
entry: './src/index',
|
||||
output: {
|
||||
path: path.resolve(__dirname, './dist'),
|
||||
filename: 'lua2js.js',
|
||||
library: 'lua2js',
|
||||
libraryTarget:'umd'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
presets: [
|
||||
["@babel/preset-env", {
|
||||
targets: {
|
||||
node: "current"
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(['dist'])
|
||||
]
|
||||
}
|
||||
Loading…
Reference in new issue