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 then local ty = vm and vm.Type if ty ~= nil then ty = ty else ty = '
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.set
nil and self.enumerable
nilendfunction PropertyDescriptor:IsSimple(noDefaults) return (self.writable
nil)) and (self.configurable
nil)) and (self.enumerable
nil))endfunction PropertyDescriptor:IsAccessorDescriptor if self
nil and self.set
nil then return false end if self.value
nil then return false end return trueendfunction PropertyDescriptor:IsGenericDescriptor if self
-- 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.prefix end end if b ~= nil and type(b) ~= 'string' then if b.prefix
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
'string' then table.insert(result, ss) ss = table.remove(stack) elseif ss.prefix
-- 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') local key = tostring(s) stringInternTable[key] = s return send
-- Convert values to/from lualocal function isJsVal(v) if type(v)
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
'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)
-- IsPropertyDescriptor (returns lua boolean, not js boolean)function IsPropertyDescriptor(v) -- faster return getmetatable(v)
-- 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
'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
-- 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
-- ToNumericfunction JsValMT.ToNumeric(env, value) local primValue = mt(env, value, 'ToPrimitive', 'number') if mt(env, primValue, 'Type')
-- 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
'+' then s = string.sub(s, 2) elseif pm
'Infinity' then return NumberMT:from(minus/0) end -- +/-Infinity local prefix = string.lower(string.sub(s, 1, 2)) if 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' then n = nil end if n
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')
0 then return argument end -- +0.0 and -0.0 if 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')
0 or -- +0.0 and -0.0 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')
0 or -- +0.0 and -0.0 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')
0 or -- +0.0 and -0.0 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
1/0 then return StringMT:intern('Infinity') end -- +Infinity if val
-- ToPropertyKeyfunction JsValMT.ToPropertyKey(env, val) local key = mt(env, val, 'ToPrimitive', 'string') if mt(env, key, 'Type')
-- 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
'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
'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
'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]]']
-- 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
-- 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')
true then 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
nil then desc = nil -- this is fast path for method lookup through prototype elseif getmetatable(fastVal)
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 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
function ObjectMT.OrdinarySetWithOwnDescriptor(env, O, P, V, Receiver, ownDesc) if ownDesc
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
-- Delete / OrdinaryDelete (9.1.10)function OrdinaryDelete(env, O, P) local desc = mt(env, O, 'GetOwnProperty', P) if desc
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 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
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')
0 and (1/index.value)
'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
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
false then if desc.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 then if desc.writable
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
function ObjectMT.ArrayDefineOwnProperty(env, A, P, desc) -- 9.4.2.1 local lengthStr = StringMT:intern('length') if mt(env, P, 'Type')
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
false then return false end local newWritable if 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)
'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')
-- 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 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')
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)
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)
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)
function JsValMT.__unm(val, _, env) -- note optional env! local num = mt(env, val, 'ToNumeric') assert(getmetatable(num)
0 and 1/n.value
-- 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' 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' 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)
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
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
0 and y
(1/y) end -- distinguish +/- 0 return (x
function StringMT.equals(l, r) return StringMT:flatten(l).suffix
function StringMT.IsZeroLength(s) local u8 = s.utf8 if u8 ~= nil then return #u8
'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
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
-- 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
-- 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 and jsval.Type(value)
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)
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))
nil or d >= R then Z = string.sub(S, 1, i-1) break end end if #Z
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
(-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 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
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
'Number' and jsv.value
-1/0 then -- special case! (node's REPL does this as well) -- normally, (-0).toString
'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
'Object' and s
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
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 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
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)
-- 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
'function' then -- native function local rv = f(myThis, args) -- handle "apply-like" natives if rawget(func, jsval.privateSlots.ISAPPLY)
'table' and f.modul ~= nil and f.func ~= nil then -- create new frame assert(jsval.Type(parentFrame)
-- 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
'function' then -- native function local rv = f(this, args) -- handle "apply-like" natives if rawget(func, jsval.privateSlots.ISAPPLY)
'table' and f.modul ~= nil and f.func ~= nil then assert(jsval.Type(parentFrame)
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 =