Module:User:Cscott/LuaTurtle explained

return (functionlocal builders = function register(name, f) builders[name] = fendregister('luaturtle.ops', function(myrequire)-- generated by TurtleScript write-lua-ops.js

local ops =

ops.byname =

ops.bynum =

-- invert byname into bynum, and combine both into root exportfor name,val in pairs(ops.byname) do ops.bynum[val] = name ops[name] = valend

return ops

end)

register('luaturtle.compat', function(myrequire)-- Compatibility functions-- (Things present in Lua 5.3 that are missing in Lua 5.1)local compat =

local string = myrequire('string')local table = myrequire('table')

function compat.combineBytes(msb, lsb) -- (msb << 8) | lsb return (msb * 256) + lsbend

function compat.splitBytes(u16) -- u16 >> 8, u16 & 0xFF local lsb = math.fmod(u16, 256) local msb = (u16 - lsb) / 256 return msb, lsbend

function compat.rshift(x, disp) -- x >> disp return math.floor(x/(2^disp))end

function compat.utf8codes(s) local len = #s local f = function(state, _) local pos = state.nextpos if pos > len then return nil, nil end local c1 = string.byte(s, pos) if c1 <= 0x7F then state.nextpos = pos + 1 return pos, c1 end c2 = string.byte(s, pos + 1) if c1 <= 0xDF then state.nextpos = pos + 2 return pos, ((c1 % 0x20) * 0x40) + (c2 % 0x40) end c3 = string.byte(s, pos + 2) if c1 <= 0xEF then state.nextpos = pos + 3 return pos, (((c1 % 0x10) * 0x40) + (c2 % 0x40)) * 0x40 + (c3 % 0x40) end c4 = string.byte(s, pos + 3) if c1 <= 0xF7 then state.nextpos = pos + 4 return pos, ((((c1 % 0x08) * 0x40) + (c2 % 0x40)) * 0x40 + (c3 % 0x40)) * 0x40 + (c4 % 0x40) end error end return f,, 0end

function compat.utf8char(...) -- utf8.char(c) result = for _,c in ipairs do local s if c <= 0x7F then s = string.char(c) else c1 = c % 0x40 cN = (c - c1) / 0x40 if c <= 0x7FF then s = string.char(cN + 0xC0, c1 + 0x80) else c2 = cN % 0x40 cN = (cN - c2) / 0x40 if c <= 0xFFFF then s = string.char(cN + 0xE0, c2 + 0x80, c1 + 0x80) else c3 = cN % 0x40 cN = (cN - c3) / 0x40 if c <= 0x10FFFF then s = string.char(cN + 0xF0, c3 + 0x80, c2 + 0x80, c1 + 0x80) else error end end end end table.insert(result, s) end return table.concat(result)end

-- unpack is a global function for Lua 5.1, otherwise use table.unpackcompat.unpack = rawget(_G, "unpack") or table.unpack

return compat

end)

register('luaturtle.jsval', function(myrequire)-- JavaScript value types-- In the future we could gain efficiency by unwrapping some of these-- primitive types, but for now let's wrap everything.local table = myrequire('table')local string = myrequire('string')

local compat = myrequire('luaturtle.compat')

local jsval =

-- private slot keyslocal function mkPrivateSlot(name) return setmetatableendlocal DEFAULTENV = mkPrivateSlot('@DEFAULTENV@')local PARENTFRAME = mkPrivateSlot('@PARENTFRAME@')local VALUE = mkPrivateSlot('@VALUE@')local ISAPPLY = mkPrivateSlot('@ISAPPLY@')local DEBUGNAME = mkPrivateSlot('@DEBUGNAME@')-- private slots in the JS standardlocal PROTOTYPE = mkPrivateSlot('Prototype')local EXTENSIBLE = mkPrivateSlot('Extensible')local BOOLEANDATA = mkPrivateSlot('BooleanData')local ERRORDATA = mkPrivateSlot('ErrorData')local NUMBERDATA = mkPrivateSlot('NumberData')local STRINGDATA = mkPrivateSlot('StringData')local SYMBOLDATA = mkPrivateSlot('SymbolData')local CALL = mkPrivateSlot('Call')local PARAMETERMAP = mkPrivateSlot('ParameterMap')

-- helper to call 'hidden' functions on metatablelocal function mt(env, v, name, ...) if true then -- debugging code, disable later for speed in this hot path local vm = getmetatable(v) if vm

nil or vm[name]

nil then local ty = vm and vm.Type if ty ~= nil then ty = ty else ty = '' end if v

nil then ty = 'nil' end error('NYI ' .. name .. ' in ' .. ty) end end return getmetatable(v)[name](env, v, ...)end

local function ThrowError(env, name, msg) if env ~= nil then error(env:newError(name, msg)) end error(name .. ': ' .. msg)endlocal function ThrowTypeError(env, msg) ThrowError(env, 'TypeError', msg) endlocal function ThrowRangeError(env, msg) ThrowError(env, 'RangeError', msg) end

local function nyi(msg) return function error('not yet implemented: ' .. msg) endend

-- PropertyDescriptor describes a slot in the JavaScript objectlocal PropertyDescriptor = PropertyDescriptor.__index = PropertyDescriptorfunction PropertyDescriptor:new(desc) -- note! the `value`, `get` and `set` fields can be present but Undefined desc = desc or setmetatable(desc, self) return descendfunction PropertyDescriptor:newSimple(value) return self:newendfunction PropertyDescriptor:newData(desc) return self:newendfunction PropertyDescriptor:newAccessor(desc) return self:newendfunction PropertyDescriptor:clone local npd = PropertyDescriptor:new npd:setFrom(self) return npdendfunction PropertyDescriptor:setFrom(P) if P.value ~= nil then self.value = P.value end if P.get ~= nil then self.get = P.get end if P.set ~= nil then self.set = P.set end if P.writable ~= nil then self.writable = P.writable end if P.enumerable ~= nil then self.enumerable = P.enumerable end if P.configurable ~= nil then self.configurable = P.configurable end return selfendfunction PropertyDescriptor:IsEmpty return self.value

nil and self.get

nil and self.set

nil and self.writable

nil and self.enumerable

nil and self.configurable

nilendfunction PropertyDescriptor:IsSimple(noDefaults) return (self.writable

true or (noDefaults and self.writable

nil)) and (self.configurable

true or (noDefaults and self.configurable

nil)) and (self.enumerable

true or (noDefaults and self.enumerable

nil))endfunction PropertyDescriptor:IsAccessorDescriptor if self

nil then return false end if self.get

nil and self.set

nil then return false end return trueendfunction PropertyDescriptor:IsDataDescriptor if self

nil then return false end if self.value

nil and self.writable

nil then return false end return trueendfunction PropertyDescriptor:IsGenericDescriptor if self

nil then return false end if (not self:IsAccessorDescriptor) and (not self:IsDataDescriptor) then return true end return falseend

-- EcmaScript language typeslocal function extendMT(oldmt) local newmt = setmetatable local metamethods = for i,name in ipairs(metamethods) do local func = rawget(oldmt, name) if func ~= nil then rawset(newmt, name, func) end end return newmtendlocal JsValMT = local UndefinedMT = extendMT(JsValMT)local NullMT = extendMT(JsValMT)local BooleanMT = extendMT(JsValMT)local StringMT = extendMT(JsValMT)local SymbolMT = extendMT(JsValMT)local NumberMT = extendMT(JsValMT)local BigIntMT = extendMT(JsValMT)local ObjectMT = extendMT(JsValMT)

local allTypes = local function copyToAll(name) for i, ty in ipairs(allTypes) do ty[name] = JsValMT[name] endend

-- Constructorslocal Undefined = setmetatable(UndefinedMT)local Null = setmetatable(NullMT)function NumberMT:from(value) return setmetatable(self)endlocal True = setmetatable(BooleanMT)local False = setmetatable(BooleanMT)function ObjectMT:create(env, proto) -- OrdinaryObjectCreate, more or less assert(proto ~= nil) return setmetatable(ObjectMT)end

-- String constructorsfunction StringMT:cons(a, b) if a ~= nil and type(a) ~= 'string' then if a.prefix

nil then a = a.suffix elseif a.suffix

nil then a = a.prefix end end if b ~= nil and type(b) ~= 'string' then if b.prefix

nil then b = b.suffix elseif b.suffix

nil then b = b.prefix end end return setmetatable(self)endfunction StringMT:fromUTF8(s) local result = local push16 = function(c) local msb, lsb = compat.splitBytes(c) table.insert(result, string.char(msb, lsb)) end for _,c in compat.utf8codes(s) do if c <= 0xD7FF or (c >= 0xE000 and c <= 0xFFFF) then push16(c) else assert(c >= 0x10000, "unpaired surrogate") c = c - 0x10000 push16(0xD800 + compat.rshift(c, 10)) push16(0xDC00 + (c % 0x400)) -- c & 0x3FF in Lua 5.3 end end local obj = StringMT:cons(nil, table.concat(result)) obj.utf8 = s -- optimization! return objendfunction StringMT:flatten(s) if s.prefix

nil then return s end local result = local stack = local ss = s while #stack > 0 do if type(ss)

'string' then table.insert(result, ss) ss = table.remove(stack) elseif ss.prefix

nil then ss = ss.suffix else table.insert(stack, ss.suffix) ss = ss.prefix end end s.prefix = nil s.suffix = table.concat(result) return send

-- String Intern tablelocal stringInternTable = function StringMT:intern(s) -- fast case local r = stringInternTable[s] if r ~= nil then return r end -- slower case if type(s)

'string' then r = StringMT:fromUTF8(s) stringInternTable[s] = r return r end -- more unusual case, called w/ a JS object assert(mt(nil, s, 'Type')

'String') local key = tostring(s) stringInternTable[key] = s return send

-- Convert values to/from lualocal function isJsVal(v) if type(v)

'table' then local mt = getmetatable(v) if mt ~= nil and mt.isJsVal

true then return true end end return falseend

local function fromLua(env, v) if isJsVal(v) then return v end -- fast path: already converted! local ty = type(v) if ty

'string' then return StringMT:fromUTF8(v) elseif ty

'number' then return NumberMT:from(v) end ThrowTypeError(env, "Can't convert Lua type " .. ty .. " to JS")endlocal function toLua(env, jsval) assert(isJsVal(jsval), jsval) return mt(env, jsval, 'toLua')end

-- lua pretty-printing (but tostring isn't a virtual dispatch by default)function JsValMT:__tostring return mt(nil, self, 'Type') endcopyToAll('__toString')function UndefinedMT:__tostring return 'undefined' endfunction NullMT:__tostring return 'null' endfunction BooleanMT:__tostring return tostring(self.value) end-- StringMT.__tostring is actually defined lower downfunction NumberMT:__tostring return tostring(self.value) endfunction PropertyDescriptor:__tostring local s = for k,v in pairs(self) do s = s .. k .. '=' .. tostring(v) .. ',' end return 'PropertyDescriptor'end

function JsValMT:toLua(env, val) ThrowTypeError(env, "Can't convert "..tostring(val).." to Lua")endfunction UndefinedMT.toLua return nil endfunction NullMT.toLua return nil endfunction BooleanMT.toLua(env, b) return b.value endfunction NumberMT.toLua(env, n) return n.value endfunction StringMT.toLua(env, s) return tostring(s) end

-- Type (returns lua string, not js string)function UndefinedMT.Type return 'Undefined' endfunction NullMT.Type return 'Null' endfunction BooleanMT.Type return 'Boolean' endfunction StringMT.Type return 'String' endfunction SymbolMT.Type return 'Symbol' endfunction NumberMT.Type return 'Number' endfunction BigIntMT.Type return 'BigInt' endfunction ObjectMT.Type return 'Object' end-- internal object typesfunction PropertyDescriptor.Type return 'PropertyDescriptor' end

-- typeoffunction UndefinedMT.typeof return StringMT:intern('undefined') endfunction NullMT.typeof return StringMT:intern('object') endfunction BooleanMT.typeof return StringMT:intern('boolean') endfunction NumberMT.typeof return StringMT:intern('number') endfunction StringMT.typeof return StringMT:intern('string') endfunction SymbolMT.typeof return StringMT:intern('symbol') endfunction BigIntMT.typeof return StringMT:intern('bigint') endfunction ObjectMT.typeof(env, obj) if rawget(obj, CALL)

nil then return StringMT:intern('object') else return StringMT:intern('function') endend

-- IsPropertyDescriptor (returns lua boolean, not js boolean)function IsPropertyDescriptor(v) -- faster return getmetatable(v)

PropertyDescriptorend

-- IsObject (returns lua boolean, not js boolean)function JsValMT.IsObject return false endfunction ObjectMT.IsObject return true end

-- ToObjectfunction UndefinedMT.ToObject(env, undef) return ThrowTypeError(env, 'ToObject on undefined')endfunction NullMT.ToObject(env, undef) return ThrowTypeError(env, 'ToObject on null')endfunction BooleanMT.ToObject(env, b, proto) local O = ObjectMT:create(env, proto or env.realm.BooleanPrototype) rawset(O, BOOLEANDATA, b) return Oendfunction StringMT.ToObject(env, s, proto) -- StringCreate 9.4.3.4 local O = ObjectMT:create(env, proto or env.realm.StringPrototype) rawset(O, STRINGDATA, s) mt(env, O, 'DefinePropertyOrThrow', StringMT:intern('length'), PropertyDescriptor:newData) setmetatable(O, getmetatable(env.realm.StringPrototype)) return OendStringMT.StringCreate = StringMT.ToObjectfunction SymbolMT.ToObject(env, s) local O = ObjectMT:create(env, env.realm.SymbolPrototype) rawset(O, SYMBOLDATA, s) return Oendfunction NumberMT.ToObject(env, num, proto) local O = ObjectMT:create(env, proto or env.realm.NumberPrototype) rawset(O, NUMBERDATA, num) return Oendfunction ObjectMT.ToObject(env, obj) return objend

-- ToPrimitive / OrdinaryToPrimitivefunction JsValMT.ToPrimitive(env, val, hint) return valendfunction ObjectMT.ToPrimitive(env, input, hint) hint = hint or 'default' if env

nil then env = rawget(input, DEFAULTENV) end -- support for lua interop if env.symbols.toPrimitive ~= nil then -- XXX symbols NYI local exoticToPrim = mt(env, input, 'GetMethod', env.symbols.toPrimitive) if not rawequal(exoticToPrim, Undefined) then local result = mt(env, exoticToPrim, 'Call', StringMT:intern(hint)) if mt(env, result, 'IsObject') then ThrowTypeError(env, 'exotic ToPrimitive not primitive') end return result end end if hint

'default' then hint = 'number' end return mt(env, input, 'OrdinaryToPrimitive', hint)end

function ObjectMT.OrdinaryToPrimitive(env, O, hint) local methodNames assert(isJsVal(O)) if hint

'string' then methodNames = else methodNames = end for i=1,2 do local method = mt(env, O, 'Get', StringMT:intern(methodNames[i])) if mt(env, method, 'IsCallable') then local status,result = env:interpretFunction(method, O,) if (not status) then error(result) end -- rethrow if not mt(env, result, 'IsObject') then return result end end end ThrowTypeError(env, 'Failed to convert to primitive')end

-- ToBoolean -- returns JS boolean, not lua booleanfunction UndefinedMT.ToBoolean return False endfunction NullMT.ToBoolean return False endfunction BooleanMT.ToBoolean(env, b) return b endfunction NumberMT.ToBoolean(env, n) n = n.value if n

0 or (n ~= n) then return False end return Trueendfunction StringMT.ToBoolean(env, s) if StringMT.IsZeroLength(s) then return False end return Trueendfunction SymbolMT.ToBoolean return True endfunction ObjectMT.ToBoolean return True end

-- ToNumericfunction JsValMT.ToNumeric(env, value) local primValue = mt(env, value, 'ToPrimitive', 'number') if mt(env, primValue, 'Type')

'BigInt' then return primValue end return mt(env, primValue, 'ToNumber')end

-- ToNumberfunction UndefinedMT.ToNumber return NumberMT:from(0/0) endfunction NullMT.ToNumber return NumberMT:from(0) endfunction BooleanMT.ToNumber(env, b) return b.number endfunction NumberMT.ToNumber(env, n) return n endfunction StringMT.ToNumber(env, s) cheat = rawget(s, 'number') if cheat ~= nil then return NumberMT:from(cheat) end -- convert from UTF16 to UTF8 s = tostring(s) -- start by trimming whitespace s = string.gsub(s, '^%s+', ) s = string.gsub(s, '%s+$', ) if #s

0 then return NumberMT:from(0) end -- empty string is +0 local minus = 1 local pm = string.sub(s, 1, 1) if pm

'+' then s = string.sub(s, 2) elseif pm

'-' then minus = -1 s = string.sub(s, 2) end if s

'Infinity' then return NumberMT:from(minus/0) end -- +/-Infinity local prefix = string.lower(string.sub(s, 1, 2)) if prefix

'0b' or prefix

'0o' then nyi(prefix .. ' number parsing') end local n = tonumber(s) -- Lua 5.1 parses some strings which JavaScript does not. if s

'inf' or s

'-inf' then n = nil end if n

nil then n = (0/0) end return NumberMT:from(minus * n)endfunction SymbolMT.ToNumber(env) ThrowTypeError(env, 'Symbol#toNumber')endfunction BigIntMT.ToNumber(env) ThrowTypeError(env, 'BigInt#toNumber')endfunction ObjectMT.ToNumber(env, argument) if env

nil then env = rawget(argument, DEFAULTENV) end -- support for lua interop local primValue = mt(env, argument, 'ToPrimitive', 'number') return mt(env, primValue, 'ToNumber')end

function JsValMT.ToInteger(env, argument) local number = mt(env, argument, 'ToNumber') assert(mt(env, number, 'Type')

'Number') return NumberMT.ToInteger(env, number)endfunction NumberMT.ToInteger(env, argument) local number = argument.value if number ~= number then return NumberMT:from(0) end -- NaN if number

0 then return argument end -- +0.0 and -0.0 if number

(1/0) or number

(-1/0) then return argument end -- Infinities local minus = (number < 0) number = math.floor(math.abs(number)) if minus then number = -number end return NumberMT:from(number)end

function JsValMT.ToInt32(env, argument) local number = mt(env, argument, 'ToNumber') assert(mt(env, number, 'Type')

'Number') return NumberMT.ToInt32(env, number)endfunction NumberMT.ToInt32(env, argument) local number = argument.value if (number ~= number or -- NaN number

0 or -- +0.0 and -0.0 number

(1/0) or number

(-1/0)) then -- Infinities return NumberMT:from(0) end number = NumberMT.ToUint32(env, argument).value if number >= 0x80000000 then number = number - 0x100000000 end return NumberMT:from(number)end

function JsValMT.ToUint32(env, argument) local number = mt(env, argument, 'ToNumber') assert(mt(env, number, 'Type')

'Number') return NumberMT.ToUint32(env, number)endfunction NumberMT.ToUint32(env, argument) local number = argument.value if (number ~= number or -- NaN number

0 or -- +0.0 and -0.0 number

(1/0) or number

(-1/0)) then -- Infinities return NumberMT:from(0) end local minus = (number < 0) number = math.floor(math.abs(number)) if minus then number = -number end return NumberMT:from(number % 0x100000000) -- (number & 0xFFFFFFFF) in Lua 5.3end

function JsValMT.ToUint16(env, argument) local number = mt(env, argument, 'ToNumber') assert(mt(env, number, 'Type')

'Number') return NumberMT.ToUint16(env, number)endfunction NumberMT.ToUint16(env, argument) local number = argument.value if (number ~= number or -- NaN number

0 or -- +0.0 and -0.0 number

(1/0) or number

(-1/0)) then -- Infinities return NumberMT:from(0) end local minus = (number < 0) number = math.floor(math.abs(number)) if minus then number = -number end return NumberMT:from(number % 0x10000) -- (number & 0xFFFF) in Lua 5.3end

-- ToStringlocal toStringVals = function JsValMT.ToString(env, val) return toStringVals[val] endfunction NumberMT.ToString(env, n) local val = n.value if val ~= val then return StringMT:intern('NaN') end -- NaN if val

0 then return StringMT:intern('0') end -- +0 and -0 if val

1/0 then return StringMT:intern('Infinity') end -- +Infinity if val

-1/0 then return StringMT:intern('-Infinity') end -- -Infinity -- Try to match the ECMAScript spec for number format -- https://262.ecma-international.org/5.1/#sec-9.8.1 local luastr = tostring(val) -- only used for very small #s. if val >= 1e-6 then luastr = string.gsub(string.gsub(string.format('%.21f', val), "0+$", ""), "%.$", "") end local s = StringMT:fromUTF8(luastr) -- fast-ish path for converting back to number for array index, etc rawset(s, 'number', val) return sendfunction StringMT.ToString(env, val) return val endfunction SymbolMT.ToString(env, val) ThrowTypeError(env, "can't convert Symbol to string")endBigIntMT.ToString = nyi('BigInt ToString')function ObjectMT.ToString(env, val) local primValue = mt(env, val, 'ToPrimitive', 'string') return mt(env, primValue, 'ToString')end

-- ToPropertyKeyfunction JsValMT.ToPropertyKey(env, val) local key = mt(env, val, 'ToPrimitive', 'string') if mt(env, key, 'Type')

'Symbol' then return key end return mt(env, key, 'ToString')endfunction NumberMT.ToPropertyKey(env, val) return NumberMT.ToString(env, val) -- sort of fast path (kinda slow)end-- Fast path for string and symbolfunction SymbolMT.ToPropertyKey(env, val) return val endfunction StringMT.ToPropertyKey(env, val) return val end

-- ToLengthfunction JsValMT.ToLength(env, val) local len = mt(env, val, 'ToInteger') -- assume unboxed if len <= 0 then return 0 end -- XXX clamp len to 2^53 - 1 return lenend

-- CanonicalNumericIndexStringfunction StringMT.CanonicalNumericIndexString(env, val) if val.number ~= nil then return val.number end -- fast path! if StringMT.equals(val, StringMT:intern("-0")) then return NumberMT:from(-1/(1/0)) -- -0.0 end local n = mt(env, val, 'ToNumber') if StringMT.equals(val, mt(env, n, 'ToString')) then return n end return Undefinedend

-- ToIndex

-- thisBooleanValue (19.3.3)function JsValMT.thisBooleanValue(env, val) ThrowTypeError(env, 'Not a boolean value!')endfunction BooleanMT.thisBooleanValue(env, value) return valueendfunction ObjectMT.thisBooleanValue(env, obj) local b = rawget(obj, BOOLEANDATA) if b

nil then ThrowTypeError(env, 'Not a boolean value!') end assert(mt(env, b, 'Type')

'Boolean') return bend

-- thisNumberValue (20.1.3)function JsValMT.thisNumberValue(env, val) ThrowTypeError(env, 'Not a number value!')endfunction NumberMT.thisNumberValue(env, value) return valueendfunction ObjectMT.thisNumberValue(env, obj) local n = rawget(obj, NUMBERDATA) if n

nil then ThrowTypeError(env, 'Not a number value!') end assert(mt(env, n, 'Type')

'Number') return nend

-- thisStringValue (21.1.3)function JsValMT.thisStringValue(env, val) ThrowTypeError(env, 'Not a string value!')endfunction StringMT.thisStringValue(env, value) return valueendfunction ObjectMT.thisStringValue(env, obj) local s = rawget(obj, STRINGDATA) if s

nil then ThrowTypeError(env, 'Not a string value!') end assert(mt(env, s, 'Type')

'String') return send

-- IsArray (returns lua boolean, not Js boolean)function JsValMT.IsArray(env) return false endfunction ObjectMT.IsArray(env, argument) -- If argument is an Array exotic object, return true local mt = getmetatable(argument) if mt ~= nil and mt['[[DefineOwnProperty]]']

ObjectMT.ArrayDefineOwnProperty then return true end -- If argument is a Proxy exotic object, then... XXX NYI return falseend

-- IsCallable (returns lua boolean, not Js boolean)function JsValMT.IsCallable(env) return false endfunction ObjectMT.IsCallable(env, argument) return (rawget(argument, CALL) ~= nil)end

-- IsConstructor (returns lua boolean, not Js boolean)function JsValMT.IsConstructor(env) return false endfunction ObjectMT.IsConstructor(env, argument) return (getmetatable(argument)['[[Construct]]'] ~= nil)end

-- IsExtensible (returns lua boolean, not js boolean)function JsValMT.IsExtensible(env) assert(false, 'IsExtensible on prim') endfunction ObjectMT.IsExtensible(env, obj) return mt(env, obj, 'IsExtensible')end

-- DefinePropertyOrThrow (7.3.8)function ObjectMT.DefinePropertyOrThrow(env, O, P, desc) local success = mt(env, O, 'DefineOwnProperty', P, desc) if not success then ThrowTypeError(env, "Can't define property") end return successend

-- GetMethod (7.3.10)function JsValMT.GetMethod(env, V, P) local func = mt(env, V, 'GetV', P) if rawequal(func, Undefined) or rawequal(func, Null) then return Undefined end if not mt(env, func, 'IsCallable') then ThrowTypeError(env, 'method not callable') end return funcend

-- HasProperty (7.3.11)function ObjectMT.HasProperty(env, O, P) return mt(env, O, 'HasProperty', P)end

-- HasOwnProperty (7.3.12)function ObjectMT.HasOwnProperty(env, O, P) local desc = mt(env, O, 'GetOwnProperty', P) if desc

nil then return False end return Trueend

-- EnumerableOwnPropertyNames (7.3.23)function ObjectMT.EnumerableOwnPropertyNames(env, O, kind) local ownKeys = mt(env, O, 'OwnPropertyKeys') local properties = for _,key in ipairs(ownKeys) do if mt(env, key, 'Type')

'String' then local desc = mt(env, O, 'GetOwnProperty', key) if desc ~= nil and desc.enumerable

true then if kind

'key' then table.insert(properties, key) else local value = mt(env, O, 'Get', key) if kind

'value' then table.insert(properties, value) else table.insert(properties, env:arrayCreate) end end end end end return propertiesend

-- Get/GetVfunction JsValMT.GetV(env, V, P) local O = mt(env, V, 'ToObject') return mt(env, O, 'Get', P, V)endfunction ObjectMT.Get(env, O, P) return mt(env, O, 'Get', P, O)endObjectMT.GetV = ObjectMT.Get -- optimization

-- Setfunction ObjectMT.Set(env, O, P, V, Throw) local success = mt(env, O, 'Set', P, V, O) if (not success) and Throw then ThrowTypeError(env, 'Failed to set') end return successend

-- HasProperty (9.1.7)-- OrdinaryHasProperty (9.1.7.1)function OrdinaryHasProperty(env, O, P) local hasOwn = mt(env, O, 'GetOwnProperty', P) if hasOwn ~= nil then return true end local parent = mt(env, O, 'GetPrototypeOf') if not rawequal(parent, Null) then return mt(env, parent, 'HasProperty', P) end return falseendObjectMT.OrdinaryHasProperty = OrdinaryHasPropertyObjectMT['[[HasProperty]]'] = OrdinaryHasProperty

function ObjectMT.OrdinaryGet(env, O, P, Receiver) -- fast path! inlined from OrdinaryGetOwnProperty impl local desc local GetOwnProperty = getmetatable(O)['[[GetOwnProperty]]'] local fastKey = rawget(P, 'key') if GetOwnProperty

OrdinaryGetOwnProperty and fastKey ~= nil then local fastVal = rawget(O, fastKey) if fastVal

nil then desc = nil -- this is fast path for method lookup through prototype elseif getmetatable(fastVal)

PropertyDescriptor then desc = fastVal -- moderately fast path for method lookup else return fastVal -- super fast path for a simple value end else desc = GetOwnProperty(env, O, P) end if desc

nil then local parent = mt(env, O, 'GetPrototypeOf') if rawequal(parent, Null) then return Undefined end return mt(env, parent, 'Get', P, Receiver) end if desc:IsDataDescriptor then if desc.value

nil then return Undefined end return desc.value end local getter = desc.get if getter

nil or rawequal(getter, Undefined) then return Undefined end return mt(env, getter, 'Call', Receiver)endObjectMT['[[Get]]'] = ObjectMT.OrdinaryGet

function ObjectMT.OrdinarySet(env, O, P, V, Receiver) -- fast path! inlined from OrdinaryGetOwnProperty impl local GetOwnProperty = getmetatable(O)['[[GetOwnProperty]]'] if rawequal(O, Receiver) and GetOwnProperty

OrdinaryGetOwnProperty then local fastKey = rawget(P, 'key') if fastKey ~= nil then local fastVal = rawget(O, fastKey) if fastVal ~= nil and getmetatable(fastVal) ~= PropertyDescriptor then -- fast path for a set of a simple value rawset(O, fastKey, V) return true end end end local ownDesc = GetOwnProperty(env, O, P) return mt(env, O, 'OrdinarySetWithOwnDescriptor', P, V, Receiver, ownDesc)endObjectMT['[[Set]]'] = ObjectMT.OrdinarySet

function ObjectMT.OrdinarySetWithOwnDescriptor(env, O, P, V, Receiver, ownDesc) if ownDesc

nil then local parent = mt(env, O, 'GetPrototypeOf') if not rawequal(parent, Null) then return mt(env, parent, 'Set', P, V, Receiver) else ownDesc = PropertyDescriptor:newSimple(Undefined) end end if ownDesc:IsDataDescriptor then if not ownDesc.writable then return false end if not mt(env, Receiver, 'IsObject') then return false end local existingDescriptor = mt(env, Receiver, 'GetOwnProperty', P) if existingDescriptor ~= nil then if existingDescriptor:IsAccessorDescriptor then return false end if existingDescriptor.writable

false then return false end local valueDesc = PropertyDescriptor:new return mt(env, Receiver, 'DefineOwnProperty', P, valueDesc) else -- inlined CreateDataProperty(Receiver, P, V) local newDesc = PropertyDescriptor:newSimple(V) return mt(env, Receiver, 'DefineOwnProperty', P, newDesc) end end -- ownDesc is an accessor descriptor local setter = ownDesc.set if setter

nil or rawequal(setter, Undefined) then return false end mt(env, setter, 'Call', Receiver, V) return trueend

-- Delete / OrdinaryDelete (9.1.10)function OrdinaryDelete(env, O, P) local desc = mt(env, O, 'GetOwnProperty', P) if desc

nil then return true end if desc.configurable

true then local field = mt(env, P, 'toKey') rawset(O, field, nil) return true end return falseendObjectMT.OrdinaryDelete = OrdinaryDeleteObjectMT['[[Delete]]'] = OrdinaryDelete

-- GetPrototypeOf / SetPrototypeOffunction OrdinaryGetPrototypeOf(env, obj) return rawget(obj, PROTOTYPE)endObjectMT.OrdinaryGetPrototypeOf = OrdinaryGetPrototypeOfObjectMT['[[GetPrototypeOf]]'] = OrdinaryGetPrototypeOf

function ObjectMT.OrdinarySetPrototypeOf(env, O, V) assert(rawequal(V, Null) or mt(env, V, 'IsObject'), 'bad prototype') local current = rawget(O, PROTOTYPE) if mt(env, V, 'SameValue', current) then return true end if rawget(O, EXTENSIBLE)

false then return false end local p = V local done = false while done

false do if rawequal(p, Null) then done = true elseif mt(env, p, 'SameValue', O) then return false -- prototype cycle! bail! else if getmetatable(p)['[[GetPrototypeOf]]'] ~= OrdinaryGetPrototypeOf then done = true else p = rawget(p, PROTOTYPE) or Null end end end rawset(O, PROTOTYPE, V) return trueendObjectMT['[[SetPrototypeOf]]'] = OrdinarySetPrototypeOf

function ObjectMT.SetImmutablePrototype(env, O, V) local current = mt(env, O, 'GetPrototypeOf') if mt(env, V, 'SameValue', current) then return true else return false endend

function ObjectMT.OrdinaryIsExtensible(env, obj) return rawget(obj, EXTENSIBLE) ~= falseendObjectMT['[[IsExtensible]]'] = ObjectMT.OrdinaryIsExtensible

function ObjectMT.OrdinaryPreventExtensions(env, obj) rawset(obj, EXTENSIBLE, false) return trueendObjectMT['[[PreventExtensions]]'] = ObjectMT.OrdinaryPreventExtensions

-- returns nil or a PropertyDescriptor-- Note that a fast path from this is inlined into Get and Setfunction OrdinaryGetOwnProperty(env, O, P) -- P is a String or a Symbol local field = mt(env, P, 'toKey') local valOrDesc = rawget(O, field) if valOrDesc

nil then return nil end if IsPropertyDescriptor(valOrDesc) then return valOrDesc else return PropertyDescriptor:newSimple(valOrDesc) endendObjectMT.OrdinaryGetOwnProperty = OrdinaryGetOwnPropertyObjectMT['[[GetOwnProperty]]'] = OrdinaryGetOwnProperty

function ObjectMT.StringGetOwnProperty(env, S, P) -- 9.4.3.5 assert(rawget(S, STRINGDATA) ~= nil) if mt(env, P, 'Type') ~= 'String' then return nil end local index = mt(env, P, 'CanonicalNumericIndexString') if rawequal(index, Undefined) then return nil end assert(mt(env, index, 'Type')

'Number') if not mt(env, index, 'IsInteger') then return nil end -- test for -0.0 if index.value

0 and (1/index.value)

(-1/0) then return nil end if index.value < 0 then return nil end local str = rawget(S, STRINGDATA) console.assert(str~=nil and mt(env, str, 'Type')

'String') local len = StringMT.__len(str) if len <= index then return nil end local start = (index * 2) + 1 -- 1-based indexing! local resultStr = string.sub(StringMT:flatten(str).suffix, start, start+1) return PropertyDescriptor:newDataend

function ObjectMT.OrdinaryDefineOwnProperty(env, O, P, Desc) local current = mt(env, O, 'GetOwnProperty', P) local extensible = mt(env, O, 'IsExtensible') return ObjectMT.ValidateAndApplyPropertyDescriptor(env, O, P, extensible, Desc, current)endObjectMT['[[DefineOwnProperty]]'] = ObjectMT.OrdinaryDefineOwnProperty

function ObjectMT.ValidateAndApplyPropertyDescriptor(env, O, P, extensible, desc, current) local field = (not rawequal(O, Undefined)) and mt(env, P, 'toKey') or nil if current

nil then if extensible

false then return false end if desc:IsGenericDescriptor or desc:IsDataDescriptor then if field ~= nil then if desc:IsSimple(false) then local val = desc.value if val

nil then val = Undefined end rawset(O, field, val) else rawset(O, field, PropertyDescriptor:newData(desc)) end end else if field ~= nil then rawset(O, field, PropertyDescriptor:newAccessor(desc)) end end return true end if desc:IsEmpty then return true end if current.configurable

false then if desc.configurable

true then return false end if desc.enumerable ~= nil and (desc.enumerable ~= current.enumerable) then return false end end if desc:IsGenericDescriptor then -- no further validation required elseif current:IsDataDescriptor ~= desc:IsDataDescriptor then if current.configurable

false then return false end if current:IsDataDescriptor then if field ~= nil then rawset(O, field, PropertyDescriptor:newAccessor(current)) end else if field ~= nil then rawset(O, field, PropertyDescriptor:newData(current)) end end elseif current:IsDataDescriptor and desc:IsDataDescriptor then if current.configurable

false and current.writable

false then if desc.writable

true then return false end if desc.value ~= nil and not mt(env, desc.value, 'SameValue', current.value) then return false end return true end else if current.configurable

false then if desc.set ~= nil and not mt(env, desc.set, 'SameValue', current.set) then return false end if desc.get ~= nil and not mt(env, desc.get, 'SameValue', current.get) then return false end return true end end if field ~= nil then local valOrDesc = rawget(O, field) if IsPropertyDescriptor(valOrDesc) then valOrDesc:setFrom(desc) elseif desc:IsSimple(true) then if desc.value ~= nil then rawset(O, field, desc.value) end -- bail early, because valOrDesc is a value not a PropertyDescriptor return true else -- valOrDesc is a value here... valOrDesc = PropertyDescriptor:newSimple(valOrDesc):setFrom(desc) -- ...and now it's a property descriptor rawset(O, field, valOrDesc) end -- if we've falled through, check once more if the resulting valOrDesc -- (guaranteed to be a PropertyDescriptor) is simple, and optimize if so if valOrDesc.value ~= nil and valOrDesc:IsSimple(false) then -- reoptimize if we've ended up with a simple field rawset(O, field, valOrDesc.value) end end return trueend

function JsValMT.ObjectDefineProperties(env) ThrowTypeError(env, "Can't define properties on non-object")endfunction ObjectMT.ObjectDefineProperties(env, O, Properties) local props = mt(env, Properties, 'ToObject') local keys = mt(env, props, 'OwnPropertyKeys') local descriptors = for _,nextKey in ipairs(keys) do local propDesc = mt(env, props, 'GetOwnProperty', nextKey) if propDesc ~= nil and propDesc.enumerable

true then local descObj = mt(env, props, 'Get', nextKey) local desc = mt(env, descObj, 'ToPropertyDescriptor') table.insert(descriptors,) end end for _,pair in ipairs(descriptors) do mt(env, O, 'DefinePropertyOrThrow', pair.key, pair.desc) end return Oend

function ObjectMT.ArrayDefineOwnProperty(env, A, P, desc) -- 9.4.2.1 local lengthStr = StringMT:intern('length') if mt(env, P, 'Type')

'String' then if StringMT.equals(P, lengthStr) then return mt(env, A, 'ArraySetLength', desc) end local index = mt(env, P, 'ToUint32') if StringMT.equals(mt(env, index, 'ToString'), P) then -- P is an array index local oldLenDesc = mt(env, A, 'OrdinaryGetOwnProperty', lengthStr) local oldLen = oldLenDesc.value if index.value >= oldLen.value and oldLenDesc.writable

false then return false end local succeeded = mt(env, A, 'OrdinaryDefineOwnProperty', P, desc) if not succeeded then return false end if index.value >= oldLen.value then local newLenDesc = PropertyDescriptor:newData(oldLenDesc) newLenDesc.value = NumberMT:from(index.value + 1) succeeded = mt(env, A, 'OrdinaryDefineOwnProperty', lengthStr, newLenDesc) assert(succeeded) end return true end end return mt(env, A, 'OrdinaryDefineOwnProperty', P, desc)end

function ObjectMT.ArraySetLength(env, A, desc) local lengthStr = StringMT:intern('length') if desc.value

nil then return mt(env, A, 'OrdinaryDefineOwnProperty', lengthStr, desc) end local newLenDesc = desc:clone local newLen = mt(env, desc.value, 'ToUint32') local numberLen = mt(env, desc.value, 'ToNumber') if newLen.value ~= numberLen.value then ThrowRangeError(env, 'array length out of range') end newLenDesc.value = newLen local oldLenDesc = mt(env, A, 'OrdinaryGetOwnProperty', lengthStr) assert(oldLenDesc ~= nil) local oldLen = oldLenDesc.value if newLen.value >= oldLen.value then return mt(env, A, 'OrdinaryDefineOwnProperty', lengthStr, newLenDesc) end if oldLenDesc.writable

false then return false end local newWritable if newLenDesc.writable

nil or newLenDesc.writable

true then newWritable = true else -- defer setting writable in case any elements can't be deleted newWritable = false newLenDesc.writable = true end local succeeded = mt(env, A, 'OrdinaryDefineOwnProperty', lengthStr, newLenDesc) if not succeeded then return false end for i=oldLen.value-1, newLen.value, -1 do -- XXX this isn't quite right; in a sparse array we waste time trying -- to delete non-existant elements. But it's close enough. local P = mt(env, NumberMT:from(i), 'ToPropertyKey') local deleteSucceeded = mt(env, A, 'Delete', P) if not deleteSucceeded then newLenDesc = newLenDesc:clone newLenDesc.value = NumberMT:from(i + 1) if not newWritable then newLenDesc.writable = false end mt(env, A, 'OrdinaryDefineOwnProperty', lengthStr, newLenDesc) return false end end if not newWritable then return mt(env, A, 'OrdinaryDefineOwnProperty', lengthStr, PropertyDescriptor:new) end return trueend

-- 9.1.11 OrdinaryOwnPropertyKeysfunction ObjectMT.OrdinaryOwnPropertyKeys(env, O) local keys = -- this is a little hacky! and order is not preserved according to JS spec for k,v in pairs(O) do if type(k)

'number' then table.insert(keys, k-1) end end -- sort in ascending numeric index order table.sort(keys) for i=1,#keys do keys[i] = tostring(keys[i]) end -- now add the non-numeric keys for k,v in pairs(O) do if type(k)

'string' then -- XXX hack, strip off the prefix table.insert(keys, string.sub(k, 4)) end end -- Convert everything to a JS string for i=1,#keys do keys[i] = StringMT:fromUTF8(keys[i]) end -- XXX now add the symbol keys return keysendObjectMT['[[OwnPropertyKeys]]'] = ObjectMT.OrdinaryOwnPropertyKeys

-- 9.1.13 OrdinaryCreateFromConstructorfunction JsValMT.OrdinaryCreateFromConstructor(env, constructor, defaultProto) return ObjectMT:create(env, defaultProto)endfunction ObjectMT.OrdinaryCreateFromConstructor(env, constructor, defaultProto) local proto = mt(env, constructor, 'GetPrototypeFromConstructor', defaultProto) return ObjectMT:create(env, proto)end

-- 9.1.14 GetPrototypeFromConstructorfunction JsValMT.GetPrototypeFromConstructor(env, constructor, defaultProto) return defaultProtoendfunction ObjectMT.GetPrototypeFromConstructor(env, constructor, defaultProto) assert(mt(env, constructor, 'IsCallable')) local proto = mt(env, constructor, 'Get', StringMT:intern('prototype')) if mt(env, proto, 'Type')

'Object' then return proto end return defaultProtoend

-- Additional methods on PropertyDescriptorfunction PropertyDescriptor.IsCompatible(desc, extensible, current) return ObjectMT.ValidateAndApplyPropertyDescriptor(nil, Undefined, Undefined, extensible, desc, current)end

function PropertyDescriptor:From(env, desc) local obj = ObjectMT:create(env, env.realm.ObjectPrototype) local mkProp = function(field, val) mt(env, obj, 'CreateDataPropertyOrThrow', StringMT:intern(field), val) end local toBool = function(b) if b then return True else return False end end if desc.value ~= nil then mkProp('value', desc.value) end if desc.writable ~= nil then mkProp('writable', toBool(desc.writable)) end if desc.get ~= nil then mkProp('get', desc.get) end if desc.set ~= nil then mkProp('get', desc.set) end if desc.enumerable ~= nil then mkProp('enumerable', toBool(desc.enumerable)) end if desc.configurable ~= nil then mkProp('configurable', toBool(desc.configurable)) end return objend

function ObjectMT.ToPropertyDescriptor(env, obj) if not mt(env, obj, 'IsObject') then ThrowTypeError(env, 'property descriptor not an object') end local desc = PropertyDescriptor:new local has = function(field) return mt(env, obj, 'HasProperty', StringMT:intern(field)) end local get = function(field) return mt(env, obj, 'Get', StringMT:intern(field)) end local getBool = function(field) return mt(env, get(field), 'ToBoolean').value end if has('enumerable') then desc.enumerable = getBool('enumerable') end if has('configurable') then desc.configurable = getBool('configurable') end if has('value') then desc.value = get('value') end -- can be Undefined if has('writable') then desc.writable = getBool('writable') end if has('get') then local getter = get('get') if (not mt(env, getter, 'IsCallable')) and not rawequal(getter, Undefined) then ThrowTypeError(env, 'getter is not callable') end desc.get = getter -- can be Undefined end if has('set') then local setter = get('set') if (not mt(env, setter, 'IsCallable')) and (not rawequal(setting, Undefined)) then ThrowTypeError(env, 'setter is not callable') end desc.set = setter end if desc.get ~= nil or desc.set ~= nil then if desc.value ~= nil or desc.writable ~= nil then ThrowTypeError(env, 'accessor or data descriptor, not both') end end return descend

-- Mathfunction JsValMT.__add(lval, rval, env) -- note optional env! assert(isJsVal(rval)) local lprim = mt(env, lval, 'ToPrimitive') if mt(env, lprim, 'Type')

'String' then -- ToPrimitive/ToString on rval done inside StringMT.__add return StringMT.__add(lprim, rval, env) -- no need for ToString on lprim end local rprim = mt(env, rval, 'ToPrimitive') if mt(env, rprim, 'Type')

'String' then local lstr = mt(env, lprim, 'ToString') return StringMT.__add(lstr, rprim, env) -- no need for ToString on rprim end local lnum = mt(env, lprim, 'ToNumeric') local rnum = mt(env, rprim, 'ToNumeric') -- avoids a redundant ToPrimitive return lnum + rnumendcopyToAll('__add')function StringMT.__add(lstr, rstr, env) if getmetatable(rstr) ~= StringMT then assert(isJsVal(rstr)) local rprim = mt(env, rstr, 'ToPrimitive') rstr = mt(env, rprim, 'ToString') end return StringMT:cons(lstr, rstr)endfunction NumberMT.__add(l, r, env) if getmetatable(r) ~= NumberMT then assert(isJsVal(r)) local rprim = mt(env, r, 'ToPrimitive') -- may be redundant if mt(env, rprim, 'Type')

'String' then -- whoops, bail to string + string case! local lstr = mt(env, l, 'ToString') return StringMT.__add(lstr, rprim, env) end r = mt(env, rprim, 'ToNumeric') end return NumberMT:from(l.value + r.value)end

function JsValMT.__sub(lval, rval, env) -- note optional env! local lnum = mt(env, lval, 'ToNumeric') local rnum = mt(env, rval, 'ToNumeric') if mt(env, lnum, 'Type') ~= mt(env, rnum, 'Type') then ThrowTypeError(env, 'bad types for subtraction') end assert(getmetatable(lnum)

NumberMT) return NumberMT:from(lnum.value - rnum.value)endcopyToAll('__sub')function NumberMT.__sub(l, r, env) if getmetatable(r) ~= NumberMT then r = mt(env, r, 'ToNumeric') end return NumberMT:from(l.value - r.value)end

function JsValMT.__div(lval, rval, env) -- note optional env! local lnum = mt(env, lval, 'ToNumeric') local rnum = mt(env, rval, 'ToNumeric') if mt(env, lnum, 'Type') ~= mt(env, rnum, 'Type') then ThrowTypeError(env, 'bad types for division') end assert(getmetatable(lnum)

NumberMT) return NumberMT:from(lnum.value / rnum.value)endcopyToAll('__div')function NumberMT.__div(l, r, env) if getmetatable(r) ~= NumberMT then r = mt(env, r, 'ToNumeric') end return NumberMT:from(l.value / r.value)end

function JsValMT.__mul(lval, rval, env) -- note optional env! local lnum = mt(env, lval, 'ToNumeric') local rnum = mt(env, rval, 'ToNumeric') if mt(env, lnum, 'Type') ~= mt(env, rnum, 'Type') then ThrowTypeError(env, 'bad types for multiplication') end assert(getmetatable(lnum)

NumberMT) return NumberMT:from(lnum.value * rnum.value)endcopyToAll('__mul')function NumberMT.__mul(l, r, env) if getmetatable(r) ~= NumberMT then r = mt(env, r, 'ToNumeric') end return NumberMT:from(l.value * r.value)end

function JsValMT.__unm(val, _, env) -- note optional env! local num = mt(env, val, 'ToNumeric') assert(getmetatable(num)

NumberMT) return NumberMT.__unm(num, num, env)endcopyToAll('__unm')function NumberMT.__unm(n, _, env) -- special case for -0 if n.value

0 and 1/n.value

1/0 then return NumberMT:from(-1/(1/0)) end return NumberMT:from(-n.value)end

-- Note that the order in which we call 'ToPrimitive' is a slight variance-- from the JS spec -- EcmaScript is strict about calling it on the left-- operand first, then the right operand -- but our turtlescript compiler-- can swap operands in the interest of simplifying the bytecode operation.function JsValMT.__lt(lval, rval, env) -- note optional env! local lnum = mt(env, lval, 'ToPrimitive', 'number') local rnum = mt(env, rval, 'ToPrimitive', 'number') -- if *both* are strings, we do a string comparison if mt(env, lnum, 'Type')

'String' and mt(env, rnum, 'Type')

'String' then return StringMT.__lt(lval, rval, env) end -- otherwise, a numerical comparison (skipping some BigInt support here) lnum = mt(env, lnum, 'ToNumeric') rnum = mt(env, rnum, 'ToNumeric') return NumberMT.__lt(lnum, rnum, env)endcopyToAll('__lt')function StringMT.__lt(l, r, env) if getmetatable(r) ~= StringMT then -- this will be a numeric comparison. return JsValMT.__lt(l, r, env) end l = StringMT:flatten(l).suffix r = StringMT:flatten(r).suffix return l < r -- This is UTF-16 but I think it works out correctlyendfunction NumberMT.__lt(l, r, env) if getmetatable(r) ~= NumberMT then r = mt(env, r, 'ToPrimitive', 'number') r = mt(env, r, 'ToNumeric') end return l.value < r.valueend

function JsValMT.__le(lval, rval, env) -- note optional env! local lnum = mt(env, lval, 'ToPrimitive', 'number') local rnum = mt(env, rval, 'ToPrimitive', 'number') -- if *both* are strings, we do a string comparison if mt(env, lnum, 'Type')

'String' and mt(env, rnum, 'Type')

'String' then return StringMT.__le(lval, rval, env) end -- otherwise, a numerical comparison (skipping some BigInt support here) lnum = mt(env, lnum, 'ToNumeric') rnum = mt(env, rnum, 'ToNumeric') return NumberMT.__le(lnum, rnum, env)endcopyToAll('__le')function StringMT.__le(l, r, env) if getmetatable(r) ~= StringMT then -- this will be a numeric comparison. return JsValMT.__le(l, r, env) end l = StringMT:flatten(l).suffix r = StringMT:flatten(r).suffix return l <= r -- This is UTF-16 but I think it works out correctlyendfunction NumberMT.__le(l, r, env) if getmetatable(r) ~= NumberMT then r = mt(env, r, 'ToPrimitive', 'number') r = mt(env, r, 'ToNumeric') end return l.value <= r.valueend

function JsValMT.__eq(l, r, env) if isJsVal(l) ~= isJsVal(r) then return false end if getmetatable(l)

NumberMT then return NumberMT.__eq(l, r, env) end return mt(env, l, 'SameValue', r)endfunction NumberMT.__eq(l, r, env) if getmetatable(r) ~= NumberMT then return false end return l.value

r.value -- matches Number::equal (6.1.6.1.13)endfunction StringMT.__eq(l, r, env) -- fast path if getmetatable(r) ~= StringMT then return false end return StringMT:flatten(l).suffix

StringMT:flatten(r).suffixend

function UndefinedMT.SameValue(env, l, r) if getmetatable(r) ~= UndefinedMT then return false end return trueendfunction NullMT.SameValue(env, l, r) if getmetatable(r) ~= NullMT then return false end return trueendfunction BooleanMT.SameValue(env, l, r) if getmetatable(r) ~= BooleanMT then return false end return l.value

r.valueendfunction StringMT.SameValue(env, l, r) if getmetatable(r) ~= StringMT then return false end return StringMT.equals(l, r)endfunction SymbolMT.SameValue(env, l, r) if getmetatable(r) ~= StringMT then return false end return rawequal(l, r)endfunction ObjectMT.SameValue(env, l, r) if mt(env, r, 'Type') ~= 'Object' then return false end -- allow subclassing return rawequal(l, r)endfunction NumberMT.SameValue(env, l, r) -- see 6.1.6.1.14 if getmetatable(r) ~= NumberMT then return false end local x, y = l.value, r.value if x ~= x and y ~= y then return true end -- both x and y are NaN if x

0 and y

0 then return (1/x)

(1/y) end -- distinguish +/- 0 return (x

y)end

function StringMT.equals(l, r) return StringMT:flatten(l).suffix

StringMT:flatten(r).suffixend

function StringMT.IsZeroLength(s) local u8 = s.utf8 if u8 ~= nil then return #u8

0 end -- fast path! for _,ss in ipairs do -- suffix's more likely non-nil if ss ~= nil then if type(ss)

'string' then if #ss > 0 then return false end else if not StringMT.IsZeroLength(ss) then return false end end end end return trueend

-- Object utilities (lua interop)function ObjectMT:__index(key) local env = rawget(self, DEFAULTENV) local jskey = mt(env, fromLua(env, key), 'ToPropertyKey') return toLua(env, mt(env, self, 'GetV', jskey))endfunction ObjectMT:__newindex(key, value) local env = rawget(self, DEFAULTENV) local jskey = mt(env, fromLua(env, key), 'ToPropertyKey') local jsval = fromLua(env, value) mt(env, self, 'Set', jskey, jsval, self)end

-- String utilities (lua interop)-- Note that #s with s an wrapped String jsval doesn't invoke this on Lua 5.1-- although it works fine on Lua 5.2+ ; use jsval.strlen(S) instead of #S-- for compatibility with Lua 5.1.function StringMT:__len if self.prefix ~= nil then StringMT:flatten(self) end return (#self.suffix) / 2 -- UTF-16 length (should be // in lua 5.3)end

-- UTF16 to UTF8 string conversionfunction StringMT:__tostring local u8 = self.utf8 if u8 ~= nil then return u8 end -- fast path for constants s = StringMT:flatten(self).suffix -- UTF-16 native string local result = local len = #s local surrogate = false for i=1,len,2 do local hi,lo = string.byte(s, i, i+1) local code = compat.combineBytes(hi, lo) if surrogate ~= false then if code >= 0xDC00 and code <= 0xDFFF then code = (surrogate - 0xDB00) * 0x400 + (code - 0xDC00) + 0x10000; surrogate = false else assert(false, 'bad utf-16') end table.insert(result, code) elseif code >= 0xDB00 and code <= 0xDBFF and (i+2) < len then surrogate = code else table.insert(result, code) end end assert(surrogate

false, 'bad utf-16') u8 = compat.utf8char(compat.unpack(result)) -- speed up future invocations! self.utf8 = u8 return u8endfunction StringMT.toKey(env, s) local key = rawget(s, 'key') if key ~= nil then return key end -- fast path key = 'js@' .. StringMT.__tostring(s) rawset(s, 'key', key) return keyend

return

end)

register('luaturtle.env', function(myrequire)-- JavaScript execution environment-- Consists of a realm, well-known symbols, etclocal ops = myrequire('luaturtle.ops')local jsval = myrequire('luaturtle.jsval')local compat = myrequire('luaturtle.compat')

function nyi(which) return function error("Not yet implemented: " .. which) endend

-- Bytecode interpreter execution statelocal State = State.__index = State

function State:new(parent, frame, modul, func) local o = setmetatable(o, self) return oend

function State:__tostring local s = for k,v in pairs(self) do s = s .. k .. '=' .. tostring(v) .. ',' end return 'State'end

function State:push(v) table.insert(self.stack, v)end

function State:pop return table.remove(self.stack)end

function State:getnext local n = self.func.bytecode[self.pc] self.pc = self.pc + 1 return nend

-- JavaScript execution environment-- Consists of a realm, well-known symbols, etc.-- (Also the bytecode interpreter!)local Env = Env.__index = Env

function Env:new local env = setmetatable(env, self)

local function setRealm(name, debugName, obj) rawset(obj, jsval.privateSlots.DEBUGNAME, debugName) env.realm[name] = obj end

-- %ObjectPrototype%, the parent of all Objects local ObjectPrototype = jsval.newObject(env, jsval.Null) jsval.extendObj(ObjectPrototype) getmetatable(ObjectPrototype)['[[SetPrototypeOf]]'] = getmetatable(ObjectPrototype)['SetImmutablePrototype'] setRealm('ObjectPrototype', '%ObjectPrototype%', ObjectPrototype)

-- 19.1.1 The Object Constructor local Object = env:addNativeFunc(nil, 'Object', 1, function(this, args, newTarget, activeFunc) if newTarget ~= nil and newTarget ~= activeFunc then return jsval.invokePrivate(env, newTarget, 'OrdinaryCreateFromConstructor', ObjectPrototype) end local value = args[1] if rawequal(value, jsval.Undefined) or rawequal(value, jsval.Null) then return jsval.newObject(env, ObjectPrototype) end return jsval.invokePrivate(env, value, 'ToObject') end) env:mkFrozen(Object, 'prototype', ObjectPrototype) env:mkHidden(ObjectPrototype, 'constructor', Object) -- cycles, whee! setRealm('Object', '%Object%', Object)

local FunctionPrototype = jsval.newObject(env, ObjectPrototype) env:mkDataDesc(FunctionPrototype, 'name',) env:mkDataDesc(FunctionPrototype, 'length',) setRealm('FunctionPrototype', '%Function.prototype%', FunctionPrototype)

-- 19.2.1 The Function Constructor local Function = jsval.newObject(env, FunctionPrototype) env:mkDataDesc(Function, 'name',) env:mkDataDesc(Function, 'length',) env:mkFrozen(Function, 'prototype', FunctionPrototype) setRealm('Function', '%Function%', Function)

-- 19.3 Boolean Objects local BooleanPrototype = jsval.newObject(env, ObjectPrototype) rawset(BooleanPrototype, jsval.privateSlots.BOOLEANDATA, jsval.False) setRealm('BooleanPrototype', '%BooleanPrototype%', BooleanPrototype)

local Boolean = env:addNativeFunc(nil, 'Boolean', 1, function(this, args, newTarget) local b = jsval.invokePrivate(env, args[1] or jsdef.Undefined, 'ToBoolean') if newTarget

nil then return b end local proto = jsval.invokePrivate(env, newTarget, 'GetPrototypeFromConstructor', BooleanPrototype) return jsval.invokePrivate(env, b, 'ToObject', proto) end) env:mkFrozen(Boolean, 'prototype', BooleanPrototype) env:mkHidden(BooleanPrototype, 'constructor', Boolean) -- cycles, whee! setRealm('Boolean', '%Boolean%', Boolean)

-- 19.5 Error objects local ErrorPrototype = jsval.newObject(env, ObjectPrototype) env:mkHidden(ErrorPrototype, 'name', 'Error') env:mkHidden(ErrorPrototype, 'message', ) setRealm('ErrorPrototype', '%ErrorPrototype%', ErrorPrototype)

local Error = env:addNativeFunc(nil, 'Error', 1, function(this, args, newTarget, activeFunc) local newTarget = newTarget or activeFunc or jsval.Undefined local O = jsval.invokePrivate(env, newTarget, 'OrdinaryCreateFromConstructor', ErrorPrototype) rawset(O, jsval.privateSlots.ERRORDATA, jsval.Undefined) if args[1] ~= nil then local msg = jsval.invokePrivate(env, args[1], 'ToString') env:mkHidden(O, 'message', msg) end return O end) env:mkFrozen(Error, 'prototype', ErrorPrototype) env:mkHidden(ErrorPrototype, 'constructor', Error) -- cycles, whee! setRealm('Error', '%Error%', Error)

local nativeErrors = for _,nativeErrorName in ipairs(nativeErrors) do local NativeErrorPrototype = jsval.newObject(env, ErrorPrototype) env:mkHidden(NativeErrorPrototype, 'name', nativeErrorName) env:mkHidden(NativeErrorPrototype, 'message', ) setRealm(nativeErrorName .. 'Prototype', '%' .. nativeErrorName .. 'Prototype%', NativeErrorPrototype)

local NativeError = env:addNativeFunc(nil, nativeErrorName, 1, function(this, args, newTarget, activeFunc) local newTarget = newTarget or activeFunc or jsval.Undefined local O = jsval.invokePrivate(env, newTarget, 'OrdinaryCreateFromConstructor', NativeErrorPrototype) rawset(O, jsval.privateSlots.ERRORDATA, jsval.Undefined) if args[1] ~= nil then local msg = jsval.invokePrivate(env, args[1], 'ToString') env:mkHidden(O, 'message', msg) end return O end) env:mkFrozen(NativeError, 'prototype', NativeErrorPrototype) env:mkHidden(NativeErrorPrototype, 'constructor', NativeError) -- cycles, whee! setRealm(nativeErrorName, '%' .. nativeErrorName .. '%', NativeError) end

-- 20.1.3 Properties of the Number Prototype Object local NumberPrototype = jsval.newObject(env, ObjectPrototype) rawset(NumberPrototype, jsval.privateSlots.NUMBERDATA, jsval.newNumber(0)) setRealm('NumberPrototype', '%NumberPrototype%', NumberPrototype)

-- 20.1.1 The Number Constructor local Number = env:addNativeFunc(nil, 'Number', 1, function(this, args, newTarget) local value = args[1] or jsval.newNumber(0) local n = jsval.invokePrivate(env, value, 'ToNumeric') -- XXX BigInt support if newTarget

nil then return n end local proto = jsval.invokePrivate(env, newTarget, 'GetPrototypeFromConstructor', NumberPrototype) return jsval.invokePrivate(env, n, 'ToObject', proto) end) env:mkFrozen(Number, 'prototype', NumberPrototype) env:mkHidden(NumberPrototype, 'constructor', Number) -- cycles, whee! setRealm('Number', '%Number%', Number)

-- 20.3 The Math object local Math = jsval.newObject(env, ObjectPrototype) setRealm('Math', '%Math%', Math)

-- 25.5 The JSON object local JSON = jsval.newObject(env, ObjectPrototype) setRealm('JSON', '%JSON%', JSON)

-- 21.1.3 Properties of the String Prototype Object local StringPrototype = jsval.newObject(env, ObjectPrototype) jsval.extendObj(StringPrototype) rawset(StringPrototype, jsval.privateSlots.STRINGDATA, jsval.newStringIntern()) env:mkFrozen(StringPrototype, 'length', 0) getmetatable(StringPrototype)['[[GetOwnProperty]]'] = function(env, S, P) local desc = jsval.invokePrivate(env, S, 'OrdinaryGetOwnProperty', P) if desc ~= nil then return desc end return jsval.invokePrivate(env, S, 'StringGetOwnProperty', P) end getmetatable(StringPrototype)['[[DefineOwnProperty]]'] = function(env, S, P, desc) local stringDesc = jsval.invokePrivate(env, S, 'StringGetOwnProperty', P) if stringDesc ~= nil then local extensible = jsval.invokePrivate(env, S, 'OrdinaryIsExtensible') return desc:IsCompatible(extensible, stringDesc) end return jsval.invokePrivate(env, S, 'OrdinaryDefineOwnProperty', P, desc) end getmetatable(StringPrototype)['[[OwnPropertyKeys]]'] = nyi('9.4.3.3') setRealm('StringPrototype', '%StringPrototype%', StringPrototype)

-- 21.1.1 The String constructor local String = env:addNativeFunc(nil, 'String', 1, function(this, args, newTarget) local value = args[1] local s if value

nil then s = jsval.newStringIntern() elseif newTarget

nil and jsval.Type(value)

'Symbol' then return jsval.invokePrivate(env, value, 'SymbolDescriptiveString') else s = jsval.invokePrivate(env, value, 'ToString') end if newTarget

nil then return s end local proto = jsval.invokePrivate(env, newTarget, 'GetPrototypeFromConstructor', StringPrototype) return jsval.invokePrivate(env, s, 'StringCreate', proto) end) env:mkFrozen(String, 'prototype', StringPrototype) env:mkHidden(StringPrototype, 'constructor', String) -- cycles, whee! setRealm('String', '%String%', String)

-- 22.1.3 Properties of the Array Prototype object local ArrayPrototype = jsval.newObject(env, ObjectPrototype) jsval.extendObj(ArrayPrototype) env:mkDataDesc(ArrayPrototype, 'length',) getmetatable(ArrayPrototype)['[[DefineOwnProperty]]'] = getmetatable(ArrayPrototype)['ArrayDefineOwnProperty'] setRealm('ArrayPrototype', '%ArrayPrototype%', ArrayPrototype)

-- 22.1.1 The Array constructor local Array = jsval.newObject(env, FunctionPrototype) env:mkFrozen(Array, 'prototype', ArrayPrototype) env:mkDataDesc(Array, 'name',) env:mkDataDesc(Array, 'length',) setRealm('Array', '%Array%', Array)

-- Functions of global this local parseInt = env:addNativeFunc(nil, 'parseInt', 2, function(this, args) local inputString = jsval.invokePrivate(env, args[1] or jsval.Undefined, 'ToString') local S = string.gsub(tostring(inputString), '^%s+', ) local sign = 1 if S ~= and string.sub(S, 1, 1)

'-' then sign = -1 end S = string.gsub(S, '^[+-]', ) local R = jsval.toLua(env, jsval.invokePrivate(env, args[2] or jsval.Undefined, 'ToInt32')) local stripPrefix = true if R

0 then R = 10 else if R < 2 or R > 36 then return jsval.newNumber(0/0) end if R ~= 16 then stripPrefix = false end end if stripPrefix then if string.lower(string.sub(S, 1, 2))

'0x' then S = string.sub(S, 3) R = 16 end end local function digit(i) local code = string.byte(S, i) if code >= 0x30 and code <= 0x39 then return code - 0x30 end -- 0-9 if code >= 0x41 and code <= 0x5A then return code - 0x41 + 10 end -- A-Z if code >= 0x61 and code <= 0x7A then return code - 0x61 + 10 end -- a-z return nil end -- scan for bad characters local Z = S for i = 1,#S do local d = digit(i) if d

nil or d >= R then Z = string.sub(S, 1, i-1) break end end if #Z

0 then return jsval.newNumber(0/0) end local number = tonumber(Z, R) assert(number ~= nil, Z .. ' radix ' .. R) -- handle -0! if sign < 0 and number

0 then return jsval.newNumber(-1/(1/0)) end return jsval.newNumber(sign * number) end) setRealm('parseInt', '%parseInt%', parseInt)

-- Not in ECMAScript but useful: console! local ConsolePrototype = jsval.newObject(env, ObjectPrototype) env.realm.ConsolePrototype = ConsolePrototype local Console = jsval.newObject(env, ConsolePrototype) setRealm('Console', '%Console%', Console)

-- Native methods local function RequireObjectCoercible(arg) if rawequal(arg, jsval.Null) or rawequal(arg, jsval.Undefined) then error(env:newTypeError('this not coercible to object')) end return arg end env:addNativeFunc(BooleanPrototype, 'valueOf', 0, function(this, args) return jsval.invokePrivate(env, this, 'thisBooleanValue') end) env:addNativeFunc(ConsolePrototype, 'log', 0, function(this, args) local sargs = for _,v in ipairs(args) do table.insert(sargs, tostring(jsval.invokePrivate(env, v, 'ToString'))) end print(table.concat(sargs, ' ')) return jsval.Undefined end) env:addNativeFunc(ErrorPrototype, 'toString', 0, function(this, args) local O = this if jsval.Type(O) ~= 'Object' then error(env:newTypeError('not object')) end local name = jsval.invokePrivate(env, O, 'Get', jsval.newStringIntern('name')) if rawequal(name, jsval.Undefined) then name = jsval.newStringIntern('Error') else name = jsval.invokePrivate(env, name, 'ToString') end local msg = jsval.invokePrivate(env, O, 'Get', jsval.newStringIntern('message')) if rawequal(msg, jsval.Undefined) then msg = jsval.newStringIntern() else msg = jsval.invokePrivate(env, msg, 'ToString') end if rawequal(jsval.invokePrivate(env, name, 'ToBoolean'), jsval.False) then return msg end if rawequal(jsval.invokePrivate(env, msg, 'ToBoolean'), jsval.False) then return name end return name + jsval.newStringIntern(': ') + msg end) env:addNativeFunc(Math, 'abs', 1, function(this, args) local n = jsval.invokePrivate(env, args[1] or jsval.Undefined, 'ToNumber') return jsval.newNumber(math.abs(jsval.toLua(env, n))) end) env:addNativeFunc(Math, 'floor', 1, function(this, args) local n = jsval.invokePrivate(env, args[1] or jsval.Undefined, 'ToNumber') n = jsval.toLua(env, n) -- special case for -0.0 if n

0 and (1/n)

(-1/0) then return jsval.newNumber(n) end return jsval.newNumber(math.floor(n)) end) env:addNativeFunc(NumberPrototype, 'valueOf', 0, function(this, args) return jsval.invokePrivate(env, this, 'thisNumberValue') end) env:addNativeFunc(Object, 'create', 2, function(this, args) local O = args[1] or jsval.Undefined local Properties = args[2] or jsval.Undefined if O ~= jsval.Null and jsval.Type(O) ~= 'Object' then error(env:newTypeError('prototype not an object or null')) end local obj = jsval.newObject(env, O) if rawequal(Properties, jsval.Undefined) then return obj end return jsval.invokePrivate(env, obj, 'ObjectDefineProperties', Properties) end) env:addNativeFunc(Object, 'defineProperties', 2, function(this, args) local O = args[1] or jsval.Undefined local Properties = args[2] or jsval.Undefined return jsval.invokePrivate(env, O, 'ObjectDefineProperties', Properties) end) env:addNativeFunc(Object, 'defineProperty', 3, function(this, args) local O = args[1] or jsval.Undefined local P = args[2] or jsval.Undefined local Attributes = args[3] or jsval.Undefined if jsval.Type(O) ~= 'Object' then error(env:newTypeError('not an object')) end local key = jsval.invokePrivate(env, P, 'ToPropertyKey') local desc = jsval.invokePrivate(env, Attributes, 'ToPropertyDescriptor') jsval.invokePrivate(env, O, 'DefinePropertyOrThrow', key, desc) return O end) env:addNativeFunc(Object, 'entries', 1, function(this, args) local O = args[1] or jsval.Undefined local obj = jsval.invokePrivate(env, O, 'ToObject') local nameList = jsval.invokePrivate(env, obj, 'EnumerableOwnPropertyNames', 'key+value') return env:arrayCreate(nameList) end) env:addNativeFunc(Object, 'keys', 1, function(this, args) local O = args[1] or jsval.Undefined local obj = jsval.invokePrivate(env, O, 'ToObject') local nameList = jsval.invokePrivate(env, obj, 'EnumerableOwnPropertyNames', 'key') return env:arrayCreate(nameList) end) env:addNativeFunc(Object, 'values', 1, function(this, args) local O = args[1] or jsval.Undefined local obj = jsval.invokePrivate(env, O, 'ToObject') local nameList = jsval.invokePrivate(env, obj, 'EnumerableOwnPropertyNames', 'value') return env:arrayCreate(nameList) end) -- Object.Try / Object.Throw -- turtlescript extension! env:addNativeFunc(Object, 'Try', 4, function(this, args) local innerThis = args[1] or jsval.Undefined local bodyBlock = args[2] or jsval.Undefined local catchBlock = args[3] or jsval.Undefined local finallyBlock = args[4] or jsval.Undefined local status, rv = env:interpretFunction(bodyBlock, innerThis,) if not status then -- exception thrown! invoke catchBlock! if not jsval.isJsVal(rv) then error(rv) end -- lua exception, rethrow -- print('EXCEPTION CAUGHT!', rv) if jsval.Type(catchBlock)

'Object' then status, rv = env:interpretFunction(catchBlock, innerThis,) -- ignore return value of catch block (not ideal) if status then rv = jsval.Undefined end end end if jsval.Type(finallyBlock)

'Object' then local finalStatus, finalResult = env:interpretFunction(finallyBlock, innerThis,) if not finalStatus then -- exceptions propagate out from finally status, rv = finalStatus, finalResult end end -- rethrow if exception uncaught (or thrown during catch/finally) if not status then error(rv) end return rv end) env:addNativeFunc(Object, 'Throw', 1, function(this, args) local ex = args[1] or jsval.Undefined error(ex) -- native throw! end)

env:addNativeFunc(ObjectPrototype, 'hasOwnProperty', 1, function(this, args) local V = args[1] or jsval.Undefined local P = jsval.invokePrivate(env, V, 'ToPropertyKey') local O = jsval.invokePrivate(env, this, 'ToObject') return jsval.invokePrivate(env, O, 'HasOwnProperty', P) end) env:addNativeFunc(ObjectPrototype, 'toString', 0, function(this, args) if rawequal(this, jsval.Undefined) then return jsval.newStringIntern('[object Undefined]') elseif rawequal(this, jsval.Null) then return jsval.newStringIntern('[object Null]') end local O = jsval.invokePrivate(env, this, 'ToObject') local isArray = jsval.invokePrivate(env, O, 'IsArray') local builtinTag if isArray then builtinTag = 'Array' elseif rawget(O, jsval.privateSlots.PARAMETERMAP) ~= nil then builtinTag = 'Arguments' elseif rawget(O, jsval.privateSlots.CALL) ~= nil then builtinTag = 'Function' elseif rawget(O, jsval.privateSlots.ERRORDATA) ~= nil then builtinTag = 'Error' elseif rawget(O, jsval.privateSlots.BOOLEANDATA) ~= nil then builtinTag = 'Boolean' elseif rawget(O, jsval.privateSlots.NUMBERDATA) ~= nil then builtinTag = 'Number' elseif rawget(O, jsval.privateSlots.STRINGDATA) ~= nil then builtinTag = 'String' -- XXX Date and RegExp, too else builtinTag = 'Object' end local tag = jsval.Undefined if env.symbols.toStringTag ~= nil then -- XXX symbols NYI tag = jsval.invokePrivate(env, O, 'Get', env.symbols.toStringTag) end if jsval.Type(tag) ~= 'String' then tag = jsval.newString(builtinTag) end return jsval.newStringIntern('[object ') + tag + jsval.newStringIntern(']') end) env:addNativeFunc(ObjectPrototype, 'valueOf', 0, function(this, args) return jsval.invokePrivate(env, this, 'ToObject') end)

rawset(env:addNativeFunc(FunctionPrototype, 'call', 1, function(this, args) -- push arguments on stack and use 'invoke' bytecode op -- arg #0 is the function itself ('this') -- arg #1 is 'this' (for the invoked function) -- arg #2-#n are rest of arguments local nargs = -- the function itself if #args

0 then -- Ensure there's a 'this' value (for the invoked function); -- that's a non-optional argument table.insert(nargs, jsval.Undefined) else for i,v in ipairs(args) do table.insert(nargs, v) end end return nargs end), jsval.privateSlots.ISAPPLY, true) rawset(env:addNativeFunc(FunctionPrototype, 'apply', 2, function(this, args) -- push arguments on stack and use 'invoke' bytecode op -- arg #0 is the function itself ('this') -- arg #1 is 'this' (for the invoked function) -- arg #2 is rest of arguments, as JS array local nargs = if #args > 1 then env:arrayEach(args[2], function(v) table.insert(nargs, v) end) end return nargs end), jsval.privateSlots.ISAPPLY, true)

env:addNativeFunc(StringPrototype, 'charAt', 1, function(this, args) local O = RequireObjectCoercible(this) local S = jsval.invokePrivate(env, O, 'ToString') local pos = args[1] or jsval.Undefined local position = jsval.toLua(env, jsval.invokePrivate(env, pos, 'ToInteger')) local size = jsval.strlen(S) if position < 0 or position >= size then return jsval.newStringIntern() end local start = (position * 2) + 1 -- 1-based indexing! local resultStr = string.sub(jsval.stringToUtf16(S), start, start + 1) return jsval.newStringFromUtf16(resultStr) end) env:addNativeFunc(StringPrototype, 'charCodeAt', 1, function(this, args) local O = RequireObjectCoercible(this) local S = jsval.invokePrivate(env, O, 'ToString') local pos = args[1] or jsval.Undefined local position = jsval.toLua(env, jsval.invokePrivate(env, pos, 'ToInteger')) local size = jsval.strlen(S) if position < 0 or position >= size then return jsval.newNumber(0/0) -- NaN end local start = (position * 2) + 1 -- 1-based indexing! local high,lo = string.byte(jsval.stringToUtf16(S), start, start + 1) return jsval.newNumber(compat.combineBytes(high, lo)) end) env:addNativeFunc(StringPrototype, 'substring', 2, function(this, args) local O = RequireObjectCoercible(this) local S = jsval.invokePrivate(env, O, 'ToString') local len = jsval.strlen(S) local startPos = args[1] or jsval.Undefined local intStart = jsval.toLua(env, jsval.invokePrivate(env, startPos, 'ToInteger')) local endPos = args[2] or jsval.Undefined local intEnd = len if not rawequal(endPos, jsval.Undefined) then intEnd = jsval.toLua(env, jsval.invokePrivate(env, endPos, 'ToInteger')) end local finalStart = math.max(0, math.min(intStart, len)) local finalEnd = math.max(0, math.min(intEnd, len)) local from = math.min(finalStart, finalEnd) local to = math.max(finalStart, finalEnd) local resultStr = string.sub(jsval.stringToUtf16(S), -- 1-based indexing (from * 2) + 1, -- last index is inclusive (not exclusive) (to * 2)) return jsval.newStringFromUtf16(resultStr) end) env:addNativeFunc(String, 'fromCharCode', 1, function(this, args) local length = #args local elements = local nextIndex = 0 while nextIndex < length do local next = args[1 + nextIndex] local nextCU = jsval.toLua(env, jsval.invokePrivate(env, next, 'ToUint16')) local msb, lsb = compat.splitBytes(nextCU) table.insert(elements, string.char(msb, lsb)) nextIndex = nextIndex + 1 end return jsval.newStringFromUtf16(table.concat(elements)) end) env:addNativeFunc(StringPrototype, 'toLowerCase', 0, function(this, args) local O = RequireObjectCoercible(this) local S = jsval.invokePrivate(env, O, 'ToString') return jsval.newString(string.lower(tostring(S))) end) env:addNativeFunc(StringPrototype, 'toUpperCase', 0, function(this, args) local O = RequireObjectCoercible(this) local S = jsval.invokePrivate(env, O, 'ToString') return jsval.newString(string.upper(tostring(S))) end) env:addNativeFunc(StringPrototype, 'valueOf', 0, function(this, args) return jsval.invokePrivate(env, this, 'thisStringValue') end)

return envend

function Env:prettyPrint(jsv) if jsv

nil then return 'nil' end assert(jsval.isJsVal(jsv)) local debugName = rawget(jsv, jsval.privateSlots.DEBUGNAME) if debugName ~= nil then return debugName end if jsval.Type(jsv)

'Number' and jsv.value

0 and 1/jsv.value

-1/0 then -- special case! (node's REPL does this as well) -- normally, (-0).toString

'0' return '-0' end if jsval.Type(jsv)

'String' then -- XXX not quite right, since the escapes are UTF-8 not UTF-16, -- but it's close return string.format('%q', tostring(jsv)) end if jsval.invokePrivate(self, jsv, 'IsArray') then local result = self:arrayEach(jsv, function(v) table.insert(result, self:prettyPrint(v)) end) if #result

0 then return '[]' end return '['..table.concat(result, ', ')..' ]' end local s = tostring(jsval.invokePrivate(self, jsv, 'ToString')) if jsval.Type(jsv)

'Object' and s

'[object Object]' then -- special case (again, node's REPL works like this too) local result = local ZERO = jsval.newStringIntern('0') local ONE = jsval.newStringIntern('1') local entries = jsval.invokePrivate(self, jsv, 'EnumerableOwnPropertyNames', 'key+value') for _,e in ipairs(entries) do local prop = jsval.invokePrivate(self, e, 'Get', ZERO) local value = jsval.invokePrivate(self, e, 'Get', ONE) -- XXX prop should be quoted iff it contains unusual characters table.insert(result, tostring(prop)..': '..self:prettyPrint(value)) end return '' end return send

function Env:arrayCreate(luaArray, isArguments) local arr = jsval.newObject(self, self.realm.ArrayPrototype) self:mkDataDesc(arr, 'length',) setmetatable(arr, getmetatable(self.realm.ArrayPrototype)) for i,v in ipairs(luaArray) do arr[i-1] = v end if isArguments

true then -- Mark this array as a special 'arguments array' -- Affects 'toString' mostly. rawset(arr, jsval.privateSlots.PARAMETERMAP, true) end return arrend

function Env:arrayEach(arr, func) local len = jsval.invokePrivate(self, arr, 'Get', jsval.newStringIntern('length')) len = jsval.toLua(env, len) for i = 1, len do local key = jsval.invokePrivate(self, jsval.newNumber(i-1), 'ToPropertyKey') local val = jsval.invokePrivate(self, arr, 'Get', key) func(val) endend

function Env.newError(env, name, msg) local O = jsval.newObject(env, env.realm[name..'Prototype']) rawset(O, jsval.privateSlots.ERRORDATA, jsval.Undefined) if msg ~= nil then msg = jsval.invokePrivate(env, jsval.fromLua(env, msg), 'ToString') env:mkHidden(O, 'message', msg) end return Oend

function Env:newTypeError(msg) return self:newError('TypeError', msg) endfunction Env:newRangeError(msg) return self:newError('RangeError', msg) end

-- helper functions to create propertiesfunction Env:mkFrozen(obj, name, value) self:mkDataDesc(obj, name, jsval.PropertyDescriptor:newData)end

function Env:mkHidden(obj, name, value) self:mkDataDesc(obj, name, jsval.PropertyDescriptor:newData)end

function Env:mkDataDesc(obj, name, desc) if getmetatable(desc) ~= jsval.PropertyDescriptor then desc = jsval.PropertyDescriptor:newData(desc) if desc.value ~= nil then desc.value = jsval.fromLua(self, desc.value) end end jsval.invokePrivate(self, obj, 'OrdinaryDefineOwnProperty', jsval.newStringIntern(name), desc)end

function Env:addNativeFunc(obj, name, len, f) local myFunc = jsval.newObject(self, self.realm.FunctionPrototype) self:mkDataDesc(myFunc, 'name',) self:mkDataDesc(myFunc, 'length',) rawset(myFunc, jsval.privateSlots.PARENTFRAME, jsval.Null) rawset(myFunc, jsval.privateSlots.VALUE, f) if obj ~= nil then self:mkHidden(obj, name, myFunc) end rawset(myFunc, jsval.privateSlots.CALL, true) -- mark as callable return myFuncend

function Env:makeTopLevelFrame(context, arguments) local frame = jsval.newFrame(self, jsval.Null, context, self:arrayCreate(arguments, true))

-- value properties of the global object self:mkHidden(frame, 'globalThis', frame) self:mkFrozen(frame, 'Infinity', 1/0) self:mkFrozen(frame, 'NaN', 0/0) self:mkFrozen(frame, 'undefined', jsval.Undefined) self:mkHidden(frame, 'console', self.realm.Console) self:mkHidden(frame, 'parseInt', self.realm.parseInt) self:addNativeFunc(frame, 'isFinite', 1, function(this, args) local number = args[1] or jsval.Undefined local num = jsval.invokePrivate(self, number, 'ToNumber') num = num.value if num ~= num then return jsval.False end -- NaN if num

1/0 or num

-1/0 then return jsval.False end -- infinities return jsval.True end) self:addNativeFunc(frame, 'isNaN', 1, function(this, args) local number = args[1] or jsval.Undefined local num = jsval.invokePrivate(self, number, 'ToNumber') num = num.value if num ~= num then return jsval.True end -- NaN return jsval.False end)

-- constructors self:mkHidden(frame, 'Array', self.realm.Array) self:mkHidden(frame, 'Boolean', self.realm.Boolean) self:mkHidden(frame, 'Error', self.realm.Error) self:mkHidden(frame, 'Function', self.realm.Function) self:mkHidden(frame, 'JSON', self.realm.JSON) self:mkHidden(frame, 'Math', self.realm.Math) self:mkHidden(frame, 'Number', self.realm.Number) self:mkHidden(frame, 'Object', self.realm.Object) self:mkHidden(frame, 'RangeError', self.realm.RangeError) self:mkHidden(frame, 'String', self.realm.String) self:mkHidden(frame, 'TypeError', self.realm.TypeError)

return frameend

local one_step =

function Env:interpretOne(state) local op = state:getnext -- print(state.func.name, state.pc-2, ops.bynum[op]) -- convert back to 0-based pc indexing local nstate = one_step[op](self, state) or state return nstateend

function Env:interpret(modul, func_id, frame) if frame

nil then frame = self:makeTopLevelFrame(jsval.Null,) end local func = modul.functions[func_id + 1] -- 1-based indexing local top = State:new(nil, frame, modul, func) local state = State:new(top, frame, modul, func) while state.parent ~= nil do -- wait for state

top state = self:interpretOne(state) end return state:popend

-- Invoke a function from the stackfunction Env:invoke(state, nargs) -- collect arguments local nativeArgs = for i = 1,nargs do table.insert(nativeArgs, state:pop) end for i = 1,compat.rshift(nargs,1) do -- reverse array j = (nargs+1) - i nativeArgs[i],nativeArgs[j] = nativeArgs[j],nativeArgs[i] end -- collect 'this' local myThis = state:pop -- get function object local func = state:pop if jsval.Type(func)

'Object' then return self:invokeInternal(state, func, myThis, nativeArgs) end error('Not a function at '..tostring(state.pc - 1)..' function '..tostring(state.func.id))end

-- Invoke a function from the stack (after function object, context, and-- arguments have been popped off the stack)function Env:invokeInternal(state, func, myThis, args) -- assert that func is a function local parentFrame = rawget(func, jsval.privateSlots.PARENTFRAME) if parentFrame

nil then error(self:newTypeError('Not a function at ' .. state.pc)) end local f = rawget(func, jsval.privateSlots.VALUE) if type(f)

'function' then -- native function local rv = f(myThis, args) -- handle "apply-like" natives if rawget(func, jsval.privateSlots.ISAPPLY)

true then local nargs = 0 for i,val in ipairs(rv) do state:push(val) nargs = nargs + 1 end return self:invoke(state, nargs - 2) end -- XXX handle exceptions state:push(rv) return state end if type(f)

'table' and f.modul ~= nil and f.func ~= nil then -- create new frame assert(jsval.Type(parentFrame)

'Object') local nFrame = jsval.newFrame(self, parentFrame, myThis, self:arrayCreate(args, true)) -- construct new child state return State:new(state, nFrame, f.modul, f.func) end error('bad function object')end

-- Returns a pair of status, result like pcall does-- status is false if an exception was thrown (and result is the exception)function Env:interpretFunction(func, this, args) -- assert that func is a function local parentFrame = rawget(func, jsval.privateSlots.PARENTFRAME) if parentFrame

nil then error(self:newTypeError('Not a function')) end local f = rawget(func, jsval.privateSlots.VALUE) if type(f)

'function' then -- native function local rv = f(this, args) -- handle "apply-like" natives if rawget(func, jsval.privateSlots.ISAPPLY)

true then local nArgs = for i,val in ipairs(rv) do table.insert(nArgs, val) end local nFunction = table.remove(nArgs, 1) local nThis = table.remove(nArgs, 1) return self:interpretFunction(nFunction, nThis, nArgs) end return true, rv end if type(f)

'table' and f.modul ~= nil and f.func ~= nil then assert(jsval.Type(parentFrame)

'Object') -- Make a frame for the function invocation local nFrame = jsval.newFrame(self, parentFrame, this, self:arrayCreate(args, true)) if true then -- lua 5.1 return pcall(function return self:interpret(f.modul, f.func.id, nFrame) end) else -- Set up error-handling return xpcall(self.interpret, debug.traceback, self, f.modul, f.func.id, nFrame) end end error('bad function object')end

return Env

end)

register('luaturtle.ifunc', function(myrequire)local ifunc =

local Function = Function.__index = Function

function Function:new(o) setmetatable(o, self) return oend

ifunc.Function = Functionreturn ifunc

end)

register('luaturtle.startup', function(myrequire)-- generated by TurtleScript write-lua-bytecode.jslocal jsval = myrequire('luaturtle.jsval')local ifunc = myrequire('luaturtle.ifunc')local ops = myrequire('luaturtle.ops')

local startup =

-- Populate the function and literal arrays with the precompiled-- startup code, including the compiler and standard library.

startup.functions = -- literalsstartup.literals =