typescript rewrite (v2)

pull/6/head
Teoxoy 6 years ago
parent 624cf557d4
commit 516327c274

@ -0,0 +1,94 @@
---
rules:
# Best Practices (https://eslint.org/docs/rules/#best-practices)
curly:
- warn
- all
dot-notation:
- error
- allowPattern: '^[a-z]+(_[a-z]+)+$'
eqeqeq:
- warn
- always
guard-for-in: error
no-new-wrappers: error
no-param-reassign: error
no-return-assign:
- error
- always
no-return-await: error
no-self-compare: error
no-sequences: error
no-throw-literal: error
no-unmodified-loop-condition: error
no-unused-expressions: error
no-useless-concat: error
no-useless-return: error
no-void: error
no-with: error
wrap-iife:
- error
- inside
yoda:
- error
- never
- exceptRange: true
# Stylistic Issues (https://eslint.org/docs/rules/#stylistic-issues)
func-style:
- error
- declaration
- allowArrowFunctions: true
no-mixed-operators:
- error
- groups:
- - '&'
- '|'
- '^'
- '~'
- '<<'
- '>>'
- '>>>'
- - '=='
- '!='
- '==='
- '!=='
- '>'
- '>='
- '<'
- '<='
- - '&&'
- '||'
- - in
- instanceof
allowSamePrecedence: true
no-multi-assign: error
no-negated-condition: error
no-nested-ternary: error
no-new-object: error
no-plusplus:
- error
- allowForLoopAfterthoughts: true
no-unneeded-ternary: error
operator-assignment:
- error
- always
prefer-object-spread: error
spaced-comment:
- warn
- always
- block:
balanced: true
# ECMAScript 6 (https://eslint.org/docs/rules/#ecmascript-6)
arrow-body-style:
- error
- as-needed
no-confusing-arrow:
- error
- allowParens: true
no-var: error
prefer-const: error
prefer-rest-params: error
prefer-spread: error
prefer-template: error

@ -0,0 +1,39 @@
---
rules:
import/no-unresolved: 2
import/named: 2
import/default: 2
import/namespace: 2
import/export: 2
import/no-named-as-default: 1
import/no-named-as-default-member: 1
import/no-duplicates: 1
import/first: 1
import/order:
- 1
- groups:
- builtin
- external
- internal
- unknown
- parent
- sibling
- index
newlines-between: never
import/no-useless-path-segments: 2
import/no-self-import: 2
import/no-absolute-path: 2
import/no-commonjs: 2
import/no-amd: 2
import/no-extraneous-dependencies:
- 2
- devDependencies: true
peerDependencies: false
optionalDependencies: false
import/exports-last: 1
import/group-exports: 1
# import/no-default-export: 2
import/no-mutable-exports: 2
import/no-named-default: 2

@ -0,0 +1,17 @@
---
rules:
'@typescript-eslint/no-useless-constructor': warn
'@typescript-eslint/restrict-plus-operands': error
'@typescript-eslint/no-use-before-define': 'off'
'@typescript-eslint/consistent-type-assertions':
- error
- assertionStyle: 'as'
objectLiteralTypeAssertions: 'allow-as-parameter'
'@typescript-eslint/explicit-member-accessibility': warn
'@typescript-eslint/explicit-function-return-type':
- warn
- allowExpressions: true
allowTypedFunctionExpressions: true
allowHigherOrderFunctions: true
'@typescript-eslint/camelcase':
- error

@ -0,0 +1,39 @@
module.exports = {
env: {
browser: true,
es6: true,
node: true
},
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
project: './tsconfig.json'
},
plugins: ['@typescript-eslint', 'import', 'prettier'],
settings: {
'import/extensions': ['.ts'],
'import/parsers': {
'@typescript-eslint/parser': ['.ts']
},
'import/resolver': {
node: {
extensions: ['.js', '.ts']
}
}
},
extends: [
'eslint:recommended',
'./.eslint.base.yml',
'plugin:@typescript-eslint/eslint-recommended', // Turn off conflicting rules
'plugin:@typescript-eslint/recommended',
'./.eslint.typescript.yml',
'prettier', // Turn off conflicting rules
'prettier/@typescript-eslint', // Turn off conflicting rules
'./.eslint.import.yml'
],
rules: {
'prettier/prettier': 'warn'
}
}

1
.gitignore vendored

@ -1 +1,2 @@
node_modules
dist

@ -0,0 +1,10 @@
module.exports = {
printWidth: 120,
tabWidth: 4,
useTabs: false,
semi: false,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: true,
arrowParens: 'avoid'
}

@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}

@ -0,0 +1,10 @@
{
"editor.formatOnSave": true,
"[typescript]": {
"editor.formatOnSave": false
},
"eslint.validate": [{ "language": "typescript", "autoFix": true }],
"eslint.autoFixOnSave": true,
"eslint.alwaysShowStatus": true,
"prettier.disableLanguages": ["ts"]
}

@ -0,0 +1,44 @@
# Contributing
First of all, thanks for your interest in helping out! 😃
# Submitting an Issue
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
We want to fix all the issues as soon as possible and a minimal reproduction scenario allows us to quickly confirm a bug (or point out a coding problem) as well as confirm that we are fixing the right problem.
# Submitting a Pull Request
## Prerequisites
- [git](https://git-scm.com/)
- [yarn](https://yarnpkg.com)
- [node](https://nodejs.org/en/)
- [vscode](https://code.visualstudio.com/)
### Note
This project uses `eslint` and `prettier` to lint and format code. I would recommend that you use `vscode` for this project because the repo already contains the extension settings for autofixing linting errors and formatting code on save. So, make sure to **download the recommended workspace extensions** in vscode after cloning the repo.
## Steps
1. Fork the repo
2. Clone your fork
3. Download the recommended workspace extensions in vscode
4. Make your changes in a new git branch (`git checkout -b my-fix-branch master`)
5. Run `yarn`
6. Run `yarn start`
7. Open the link in a browser or use the vscode debugger
8. Make changes
9. Commit your changes using a descriptive commit message
10. Push your branch to GitHub `git push origin my-fix-branch`
11. Start a pull request from GitHub
That's it! 🎉 Thank you for your contribution! 😃
## Working on your first Pull Request?
Check out this [tutorial](https://github.com/firstcontributions/first-contributions/blob/master/github-windows-vs-code-tutorial.md)
Also, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) for a more in depth (video) tutorial

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 Tanasoaia Teodor Andrei
Copyright (c) 2019 Tanasoaia Teodor Andrei
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,11 @@
# lua-in-js
A Lua to JS transpiler / runtime
[![npm](https://img.shields.io/npm/v/lua-in-js.svg?style=flat-square)](https://www.npmjs.com/package/lua-in-js)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-blue.svg?style=flat-square)](./CONTRIBUTING.md)
[![GitHub](https://img.shields.io/github/license/teoxoy/lua-in-js.svg?style=flat-square&color=blue)](./LICENSE)
&nbsp;&nbsp;_Badges are clickable!_
A Lua to JS transpiler/runtime. This library is a rewrite of [Starlight](https://github.com/paulcuth/starlight) with a lot of improvements.
## Install
@ -8,43 +13,68 @@ A Lua to JS transpiler / runtime
npm i lua-in-js
```
Usage example (I am using it just to convert a lua file to js; run the temporary js file and get some data from it):
## API
### Import
```js
const luainjs = require(luainjs)
// or
import luainjs from 'luainjs'
```
### Create the lua environment
Lua environments are isolated from each other (they got different global scopes)
```js
const luaEnv = luainjs.createEnv()
```
A config object can be passed in for extra functionality
```js
const fs = require('fs')
const lua2injs = require('lua-in-js')
const execSync = require('child_process').execSync
const data = luainjs.parser.parse(mainFileData)
fs.writeFileSync('./temp.js', `
require("lua-in-js").runtime;
${data}
require("fs").writeFileSync('./temp.json', JSON.stringify(Tget($get($, 'data'), 'raw').toObject(), null, 2));
`)
execSync('node temp.js')
const luaEnv = luainjs.createEnv({
LUA_PATH, // default value of package.path
fileExists, // function that takes in a path and returns a boolean
loadFile, // function that takes in a path and returns the content of a file
stdin, // string representing the standard input
stdout, // function representing the standard output
osExit // function called by os.exit
})
```
This library is based on the sourcecode of this library: https://github.com/paulcuth/starlight, which is under this license:
### Run a script or file
```js
luaEnv.run('print(\'Hello world!\')')
```
The MIT License (MIT)
Copyright 2015—2016 Paul Cuthbertson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```js
luaEnv.runfile('somefile.lua')
```
`runfile` uses `config.fileExists` and `config.loadFile`
## Example
Check out the [test runner](./tests/test.js) for a concrete example.
## Missing functionality
- coroutine library
- debug library
- utf8 library
- io library
- package.cpath
- package.loadlib
- string.dump
- string.pack
- string.packsize
- string.unpack
- os.clock
- os.execute
- os.getenv
- os.remove
- os.rename
- os.tmpname

1
dist/lua-in-js.js vendored

File diff suppressed because one or more lines are too long

5002
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,34 +1,50 @@
{
"name": "lua-in-js",
"version": "1.1.0",
"version": "2.0.0",
"description": "A Lua to JS transpiler/runtime",
"keywords": [
"lua",
"javascript",
"typescript",
"transpiler",
"runtime"
"runtime",
"parser"
],
"author": "Teoxoy",
"license": "MIT",
"main": "dist/lua-in-js.js",
"module": "src/index.js",
"main": "dist/lua-in-js.cjs.js",
"module": "dist/lua-in-js.es.js",
"typings": "dist/types/index.d.ts",
"repository": {
"type": "git",
"url": "git://github.com/teoxoy/lua-in-js.git"
},
"scripts": {
"build": "webpack"
"build": "rimraf dist && rollup -c rollup.config.js",
"dev": "rimraf dist && rollup -c rollup.config.js -w",
"test": "node ./tests/test.js",
"lint": "eslint . --ignore-path .gitignore --ext .ts",
"lint:fix": "eslint . --ignore-path .gitignore --ext .ts --fix"
},
"dependencies": {
"printf": "^0.5.1",
"luaparse": "^0.2.1"
"luaparse": "^0.2.1",
"printj": "^1.2.2"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/preset-env": "^7.2.3",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^1.0.0",
"webpack": "^4.28.4",
"webpack-cli": "^3.2.1"
"@types/luaparse": "^0.2.6",
"@types/node": "^12.7.4",
"@typescript-eslint/eslint-plugin": "^2.1.0",
"@typescript-eslint/parser": "^2.1.0",
"eslint": "^6.3.0",
"eslint-config-prettier": "^6.2.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-prettier": "^3.1.0",
"prettier": "1.18.2",
"rimraf": "^3.0.0",
"rollup": "^1.20.3",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.24.0",
"typescript": "^3.6.2"
}
}

@ -0,0 +1,33 @@
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import typescript from 'rollup-plugin-typescript2'
const pkg = require('./package.json')
export default {
input: 'src/index.ts',
output: [
{
file: pkg.main,
name: pkg.name,
format: 'cjs',
sourcemap: true
},
{
file: pkg.module,
name: pkg.name,
format: 'es',
sourcemap: true
}
],
external: Object.keys(pkg.dependencies).concat(require('module').builtinModules),
watch: {
include: 'src/**'
},
plugins: [
commonjs(),
resolve(),
typescript({
useTsconfigDeclarationDir: true
})
]
}

@ -0,0 +1,10 @@
export class LuaError extends Error {
public constructor(message: string) {
super()
this.message = message
}
public toString(): string {
return `LuaError: ${this.message}`
}
}

@ -0,0 +1,42 @@
import { hasOwnProperty, LuaType } from './utils'
export class Scope {
private parent: Scope
private _varargs: LuaType[]
private readonly _variables: Record<string, LuaType>
public constructor(variables = {}) {
this._variables = variables
}
public get(key: string): LuaType {
return this._variables[key]
}
public set(key: string, value: LuaType): void {
if (hasOwnProperty(this._variables, key) || !this.parent) {
this.setLocal(key, value)
} else {
this.parent.set(key, value)
}
}
public setLocal(key: string, value: LuaType): void {
this._variables[key] = value
}
public setVarargs(args: LuaType[]): void {
this._varargs = args
}
public getVarargs(): LuaType[] {
return this._varargs || (this.parent && this.parent.getVarargs()) || []
}
public extend(): Scope {
const innerVars = Object.create(this._variables)
const scope = new Scope(innerVars)
scope.parent = this
return scope
}
}

@ -0,0 +1,244 @@
import { hasOwnProperty, LuaType, coerceArgToTable, coerceToString } from './utils'
type MetaMethods =
// unary op
| '__unm'
| '__bnot'
| '__len'
// binary op
| '__add'
| '__sub'
| '__mul'
| '__mod'
| '__pow'
| '__div'
| '__idiv'
| '__band'
| '__bor'
| '__bxor'
| '__shl'
| '__shr'
| '__concat'
| '__eq'
| '__lt'
| '__le'
// other
| '__index'
| '__newindex'
| '__call'
| '__pairs'
| '__ipairs'
| '__tostring'
class Table {
public numValues: LuaType[] = [undefined]
public strValues: Record<string, LuaType> = {}
public keys: string[] = []
public values: LuaType[] = []
public metatable: Table | null = null
public constructor(initialiser?: Record<string, LuaType> | LuaType[] | ((t: Table) => void)) {
if (initialiser === undefined) return
if (typeof initialiser === 'function') {
initialiser(this)
return
}
if (Array.isArray(initialiser)) {
this.insert(...initialiser)
return
}
for (const key in initialiser) {
if (hasOwnProperty(initialiser, key)) {
let value = initialiser[key]
if (value === null) value = undefined
this.set(key, value)
}
}
}
public get(key: LuaType): LuaType {
const value = this.rawget(key)
if (value === undefined && this.metatable) {
const mm = this.metatable.get('__index') as Table | Function
if (mm instanceof Table) {
return mm.get(key)
}
if (typeof mm === 'function') {
const v = mm.call(undefined, this, key)
return v instanceof Array ? v[0] : v
}
}
return value
}
public rawget(key: LuaType): LuaType {
switch (typeof key) {
case 'string':
if (hasOwnProperty(this.strValues, key)) {
return this.strValues[key]
}
break
case 'number':
if (key > 0 && key % 1 === 0) {
return this.numValues[key]
}
}
const index = this.keys.indexOf(tostring(key))
return index === -1 ? undefined : this.values[index]
}
public getMetaMethod(name: MetaMethods): Function {
return this.metatable && (this.metatable.rawget(name) as Function)
}
public set(key: LuaType, value: LuaType): LuaType {
const mm = this.metatable && this.metatable.get('__newindex')
if (mm) {
const oldValue = this.rawget(key)
if (oldValue === undefined) {
if (mm instanceof Table) {
return mm.set(key, value)
}
if (typeof mm === 'function') {
return mm(this, key, value)
}
}
}
this.rawset(key, value)
}
public setFn(key: string): (v: LuaType) => void {
return v => this.set(key, v)
}
public rawset(key: LuaType, value: LuaType): void {
switch (typeof key) {
case 'string':
this.strValues[key] = value
return
case 'number':
if (key > 0 && key % 1 === 0) {
this.numValues[key] = value
return
}
}
const K = tostring(key)
const index = this.keys.indexOf(K)
if (index > -1) {
this.values[index] = value
return
}
this.values[this.keys.length] = value
this.keys.push(K)
}
public insert(...values: LuaType[]): void {
this.numValues.push(...values)
}
public toObject(): unknown[] | Record<string, unknown> {
const outputAsArray = Object.keys(this.strValues).length === 0 && getn(this) > 0
const result: unknown[] | Record<string, unknown> = outputAsArray ? [] : {}
for (let i = 1; i < this.numValues.length; i++) {
const propValue = this.numValues[i]
const value = propValue instanceof Table ? propValue.toObject() : propValue
if (outputAsArray) {
const res = result as unknown[]
res[i - 1] = value
} else {
const res = result as Record<string, unknown>
res[String(i - 1)] = value
}
}
for (const key in this.strValues) {
if (hasOwnProperty(this.strValues, key)) {
const propValue = this.strValues[key]
const value = propValue instanceof Table ? propValue.toObject() : propValue
const res = result as Record<string, unknown>
res[key] = value
}
}
return result
}
}
function tostring(v: LuaType): string {
if (v instanceof Table) {
const mm = v.getMetaMethod('__tostring')
if (mm) return mm(v)[0]
return valToStr(v, 'table: 0x')
}
if (v instanceof Function) {
return valToStr(v, 'function: 0x')
}
return coerceToString(v)
function valToStr(v: LuaType, prefix: string): string {
const s = v.toString()
if (s.indexOf(prefix) > -1) return s
const str = prefix + Math.floor(Math.random() * 0xffffffff).toString(16)
v.toString = () => str
return str
}
}
function getn(table: LuaType): number {
const TABLE = coerceArgToTable(table, 'getn', 1)
const vals = TABLE.numValues
const keys: boolean[] = []
for (const i in vals) {
if (hasOwnProperty(vals, i)) {
keys[i] = true
}
}
let j = 0
while (keys[j + 1]) {
j += 1
}
// Following translated from ltable.c (http://www.lua.org/source/5.3/ltable.c.html)
if (j > 0 && vals[j] === undefined) {
/* there is a boundary in the array part: (binary) search for it */
let i = 0
while (j - i > 1) {
const m = Math.floor((i + j) / 2)
if (vals[m] === undefined) {
j = m
} else {
i = m
}
}
return i
}
return j
}
export { MetaMethods, Table, getn, tostring }

@ -1,7 +0,0 @@
import * as parser from './parser'
import * as runtime from './runtime'
export {
parser,
runtime
}

@ -0,0 +1,98 @@
import { Scope } from './Scope'
import { createG } from './lib/globals'
import { operators } from './operators'
import { Table } from './Table'
import { LuaError } from './LuaError'
import { libMath } from './lib/math'
import { libTable } from './lib/table'
import { libString, metatable as stringMetatable } from './lib/string'
import { getLibOS } from './lib/os'
import { getLibPackage } from './lib/package'
import { LuaType, ensureArray, Config } from './utils'
import { parse } from './parser'
const call = (f: Function | Table, ...args: LuaType[]): LuaType[] => {
if (f instanceof Function) return ensureArray(f(...args))
const mm = f instanceof Table && f.getMetaMethod('__call')
if (mm) return ensureArray(mm(f, ...args))
throw new LuaError(`attempt to call an uncallable type`)
}
const stringTable = new Table()
stringTable.metatable = stringMetatable
const get = (t: Table | string, v: LuaType): LuaType => {
if (t instanceof Table) return t.get(v)
if (typeof t === 'string') return stringTable.get(v)
throw new LuaError(`no table or metatable found for given type`)
}
const execChunk = (_G: Table, chunk: string, chunkName?: string): LuaType[] => {
const exec = new Function('__lua', chunk)
const globalScope = new Scope(_G.strValues).extend()
if (chunkName) globalScope.setVarargs([chunkName])
const res = exec({
globalScope,
...operators,
Table,
call,
get
})
return res === undefined ? [undefined] : res
}
function createEnv(
config: Config = {}
): {
run: (script: string) => LuaType
runfile: (path: string) => LuaType
} {
const cfg: Config = {
LUA_PATH: './?.lua',
stdin: '',
stdout: console.log,
...config
}
const _G = createG(cfg, execChunk)
const { libPackage, _require } = getLibPackage(
(content, moduleName) => execChunk(_G, parse(content), moduleName)[0],
cfg
)
const loaded = libPackage.get('loaded') as Table
const load = (name: string, value: Table): void => {
_G.rawset(name, value)
loaded.rawset(name, value)
}
load('_G', _G)
load('package', libPackage)
load('math', libMath)
load('table', libTable)
load('string', libString)
load('os', getLibOS(cfg))
_G.rawset('require', _require)
const run = (script: string): LuaType => execChunk(_G, parse(script))[0]
const runfile = (filename: string): LuaType => {
if (!cfg.fileExists) throw new LuaError('runfile requires the config.fileExists function')
if (!cfg.loadFile) throw new LuaError('runfile requires the config.loadFile function')
if (!cfg.fileExists(filename)) throw new LuaError('file not found')
return run(cfg.loadFile(filename))
}
return {
run,
runfile
}
}
export { createEnv }

@ -0,0 +1,406 @@
import { parse } from '../parser'
import { Table, tostring, getn } from '../Table'
import { LuaError } from '../LuaError'
import {
LuaType,
Config,
type,
posrelat,
coerceToNumber,
coerceToString,
coerceToBoolean,
coerceArgToNumber,
coerceArgToString,
coerceArgToTable,
hasOwnProperty
} from '../utils'
import { metatable as stringMetatable } from './string'
const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
function ipairsIterator(table: Table, index: number): LuaType[] {
if (index === undefined) {
throw new LuaError('Bad argument #2 to ipairs() iterator')
}
const nextIndex = index + 1
const numValues = table.numValues
if (!numValues[nextIndex] || numValues[nextIndex] === undefined) return undefined
return [nextIndex, numValues[nextIndex]]
}
const _VERSION = 'Lua 5.3'
function assert(v: LuaType, m?: LuaType): [unknown, unknown] {
if (coerceToBoolean(v)) return [v, m]
const msg = m === undefined ? 'Assertion failed!' : coerceArgToString(m, 'assert', 2)
throw new LuaError(msg)
}
function collectgarbage(): [] {
// noop
return []
}
function error(message: LuaType): void {
const msg = coerceArgToString(message, 'error', 1)
throw new LuaError(msg)
}
/**
* If object does not have a metatable, returns nil.
* Otherwise, if the object's metatable has a __metatable field, returns the associated value.
* Otherwise, returns the metatable of the given object.
*/
function getmetatable(table: LuaType): Table {
if (table instanceof Table && table.metatable) {
const mm = table.metatable.rawget('__metatable') as Table
return mm ? mm : table.metatable
}
if (typeof table === 'string') {
return stringMetatable
}
}
/**
* Returns three values (an iterator function, the table t, and 0) so that the construction
*
* `for i,v in ipairs(t) do body end`
*
* will iterate over the keyvalue pairs (1,t[1]), (2,t[2]), ..., up to the first nil value.
*/
function ipairs(t: LuaType): [Function, Table, number] {
const table = coerceArgToTable(t, 'ipairs', 1)
const mm = table.getMetaMethod('__pairs') || table.getMetaMethod('__ipairs')
return mm ? mm(table).slice(0, 3) : [ipairsIterator, table, 0]
}
/**
* Allows a program to traverse all fields of a table.
* Its first argument is a table and its second argument is an index in this table.
* next returns the next index of the table and its associated value.
* When called with nil as its second argument, next returns an initial index and its associated value.
* When called with the last index, or with nil in an empty table, next returns nil.
* If the second argument is absent, then it is interpreted as nil.
* In particular, you can use next(t) to check whether a table is empty.
*
* The order in which the indices are enumerated is not specified, even for numeric indices.
* (To traverse a table in numerical order, use a numerical for.)
*
* The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table.
* You may however modify existing fields. In particular, you may clear existing fields.
*/
function next(table: LuaType, index?: LuaType): [number | string, LuaType] {
const TABLE = coerceArgToTable(table, 'next', 1)
// SLOOOOOOOW...
let found = index === undefined
if (found || (typeof index === 'number' && index > 0)) {
const numValues = TABLE.numValues
const keys = Object.keys(numValues)
let i = 1
if (!found) {
const I = keys.indexOf(`${index}`)
if (I >= 0) {
found = true
i += I
}
}
if (found) {
for (i; keys[i] !== undefined; i++) {
const key = Number(keys[i])
const value = numValues[key]
if (value !== undefined) return [key, value]
}
}
}
for (const i in TABLE.strValues) {
if (hasOwnProperty(TABLE.strValues, i)) {
if (!found) {
if (i === index) found = true
} else if (TABLE.strValues[i] !== undefined) {
return [i, TABLE.strValues[i]]
}
}
}
for (const i in TABLE.keys) {
if (hasOwnProperty(TABLE.keys, i)) {
const key = TABLE.keys[i]
if (!found) {
if (key === index) found = true
} else if (TABLE.values[i] !== undefined) {
return [key, TABLE.values[i]]
}
}
}
}
/**
* If t has a metamethod __pairs, calls it with t as argument and returns the first three results from the call.
*
* Otherwise, returns three values: the next function, the table t, and nil, so that the construction
*
* `for k,v in pairs(t) do body end`
*
* will iterate over all keyvalue pairs of table t.
*
* See function next for the caveats of modifying the table during its traversal.
*/
function pairs(t: LuaType): [Function, Table, undefined] {
const table = coerceArgToTable(t, 'pairs', 1)
const mm = table.getMetaMethod('__pairs')
return mm ? mm(table).slice(0, 3) : [next, table, undefined]
}
/**
* Calls function f with the given arguments in protected mode.
* This means that any error inside f is not propagated;
* instead, pcall catches the error and returns a status code.
* Its first result is the status code (a boolean), which is true if the call succeeds without errors.
* In such case, pcall also returns all results from the call, after this first result.
* In case of any error, pcall returns false plus the error message.
*/
function pcall(f: LuaType, ...args: LuaType[]): [false, string] | [true, ...LuaType[]] {
if (typeof f !== 'function') {
throw new LuaError('Attempt to call non-function')
}
try {
return [true, ...f(...args)]
} catch (e) {
return [false, e && e.toString()]
}
}
/**
* Checks whether v1 is equal to v2, without invoking the __eq metamethod. Returns a boolean.
*/
function rawequal(v1: LuaType, v2: LuaType): boolean {
return v1 === v2
}
/**
* Gets the real value of table[index], without invoking the __index metamethod.
* table must be a table; index may be any value.
*/
function rawget(table: LuaType, index: LuaType): LuaType {
const TABLE = coerceArgToTable(table, 'rawget', 1)
return TABLE.rawget(index)
}
/**
* Returns the length of the object v, which must be a table or a string, without invoking the __len metamethod.
* Returns an integer.
*/
function rawlen(v: LuaType): number {
if (v instanceof Table) return getn(v)
if (typeof v === 'string') return v.length
throw new LuaError('attempt to get length of an unsupported value')
}
/**
* Sets the real value of table[index] to value, without invoking the __newindex metamethod.
* table must be a table, index any value different from nil and NaN, and value any Lua value.
*
* This function returns table.
*/
function rawset(table: LuaType, index: LuaType, value: LuaType): Table {
const TABLE = coerceArgToTable(table, 'rawset', 1)
if (index === undefined) throw new LuaError('table index is nil')
TABLE.rawset(index, value)
return TABLE
}
/**
* If index is a number, returns all arguments after argument number index;
* a negative number indexes from the end (-1 is the last argument).
* Otherwise, index must be the string "#", and select returns the total number of extra arguments it received.
*/
function select(index: number | '#', ...args: LuaType[]): LuaType[] | number {
if (index === '#') {
return args.length
}
if (typeof index === 'number') {
const pos = posrelat(Math.trunc(index), args.length)
return args.slice(pos - 1)
}
throw new LuaError(`bad argument #1 to 'select' (number expected, got ${type(index)})`)
}
/**
* Sets the metatable for the given table.
* (To change the metatable of other types from Lua code,you must use the debug library (§6.10).)
* If metatable is nil, removes the metatable of the given table.
* If the original metatable has a __metatable field, raises an error.
*
* This function returns table.
*/
function setmetatable(table: LuaType, metatable: LuaType): Table {
const TABLE = coerceArgToTable(table, 'setmetatable', 1)
if (TABLE.metatable && TABLE.metatable.rawget('__metatable')) {
throw new LuaError('cannot change a protected metatable')
}
TABLE.metatable = coerceArgToTable(metatable, 'setmetatable', 2)
return TABLE
}
/**
* When called with no base, tonumber tries to convert its argument to a number.
* If the argument is already a number or a string convertible to a number,
* then tonumber returns this number; otherwise, it returns nil.
*
* The conversion of strings can result in integers or floats,
* according to the lexical conventions of Lua (see §3.1).
* (The string may have leading and trailing spaces and a sign.)
*
* When called with base, then e must be a string to be interpreted as an integer numeral in that base.
* The base may be any integer between 2 and 36, inclusive.
* In bases above 10, the letter 'A' (in either upper or lower case) represents 10,
* 'B' represents 11, and so forth, with 'Z' representing 35.
* If the string e is not a valid numeral in the given base, the function returns nil.
*/
function tonumber(e: LuaType, base: LuaType): number {
const E = coerceToString(e).trim()
const BASE = base === undefined ? 10 : coerceArgToNumber(base, 'tonumber', 2)
if (BASE !== 10 && E === 'nil') {
throw new LuaError("bad argument #1 to 'tonumber' (string expected, got nil)")
}
if (BASE < 2 || BASE > 36) {
throw new LuaError(`bad argument #2 to 'tonumber' (base out of range)`)
}
if (E === '') return
if (BASE === 10) return coerceToNumber(E)
const pattern = new RegExp(`^${BASE === 16 ? '(0x)?' : ''}[${CHARS.substr(0, BASE)}]*$`, 'gi')
if (!pattern.test(E)) return // Invalid
return parseInt(E, BASE)
}
/**
* This function is similar to pcall, except that it sets a new message handler msgh.
*/
function xpcall(f: LuaType, msgh: LuaType, ...args: LuaType[]): [false, string] | [true, ...LuaType[]] {
if (typeof f !== 'function' || typeof msgh !== 'function') {
throw new LuaError('Attempt to call non-function')
}
try {
return [true, ...f(...args)]
} catch (e) {
return [false, msgh(e)[0]]
}
}
function createG(cfg: Config, execChunk: (_G: Table, chunk: string) => LuaType[]): Table {
function print(...args: LuaType[]): void {
const output = args.map(arg => tostring(arg)).join('\t')
cfg.stdout(output)
}
function load(
chunk: LuaType,
_chunkname?: string,
_mode?: 'b' | 't' | 'bt',
env?: Table
): [undefined, string] | (() => LuaType[]) {
let C = ''
if (chunk instanceof Function) {
let ret = ' '
while (ret !== '' && ret !== undefined) {
C += ret
ret = chunk()[0]
}
} else {
C = coerceArgToString(chunk, 'load', 1)
}
let parsed: string
try {
parsed = parse(C)
} catch (e) {
return [undefined, e.message]
}
return () => execChunk(env || _G, parsed)
}
function dofile(filename?: LuaType): LuaType[] {
const res = loadfile(filename)
if (Array.isArray(res) && res[0] === undefined) {
throw new LuaError(res[1])
}
const exec = res as () => LuaType[]
return exec()
}
function loadfile(
filename?: LuaType,
mode?: 'b' | 't' | 'bt',
env?: Table
): [undefined, string] | (() => LuaType[]) {
const FILENAME = filename === undefined ? cfg.stdin : coerceArgToString(filename, 'loadfile', 1)
if (!cfg.fileExists) {
throw new LuaError('loadfile requires the config.fileExists function')
}
if (!cfg.fileExists(FILENAME)) return [undefined, 'file not found']
if (!cfg.loadFile) {
throw new LuaError('loadfile requires the config.loadFile function')
}
return load(cfg.loadFile(FILENAME), FILENAME, mode, env)
}
const _G = new Table({
_VERSION,
assert,
dofile,
collectgarbage,
error,
getmetatable,
ipairs,
load,
loadfile,
next,
pairs,
pcall,
print,
rawequal,
rawget,
rawlen,
rawset,
select,
setmetatable,
tonumber,
tostring,
type,
xpcall
})
return _G
}
export { tostring, createG }

@ -0,0 +1,241 @@
import { Table } from '../Table'
import { coerceArgToNumber, LuaType, coerceToNumber } from '../utils'
const maxinteger = Number.MAX_SAFE_INTEGER
const mininteger = Number.MIN_SAFE_INTEGER
const huge = Infinity
const pi = Math.PI
let randomSeed = 1
function getRandom(): number {
randomSeed = (16807 * randomSeed) % 2147483647
return randomSeed / 2147483647
}
function abs(x: LuaType): number {
const X = coerceArgToNumber(x, 'abs', 1)
return Math.abs(X)
}
function acos(x: LuaType): number {
const X = coerceArgToNumber(x, 'acos', 1)
return Math.acos(X)
}
function asin(x: LuaType): number {
const X = coerceArgToNumber(x, 'asin', 1)
return Math.asin(X)
}
function atan(y: LuaType, x?: LuaType): number {
const Y = coerceArgToNumber(y, 'atan', 1)
const X = x === undefined ? 1 : coerceArgToNumber(x, 'atan', 2)
return Math.atan2(Y, X)
}
function atan2(y: LuaType, x: LuaType): number {
return atan(y, x)
}
function ceil(x: LuaType): number {
const X = coerceArgToNumber(x, 'ceil', 1)
return Math.ceil(X)
}
function cos(x: LuaType): number {
const X = coerceArgToNumber(x, 'cos', 1)
return Math.cos(X)
}
function cosh(x: LuaType): number {
const X = coerceArgToNumber(x, 'cosh', 1)
return (exp(X) + exp(-X)) / 2
}
function deg(x: LuaType): number {
const X = coerceArgToNumber(x, 'deg', 1)
return (X * 180) / Math.PI
}
function exp(x: LuaType): number {
const X = coerceArgToNumber(x, 'exp', 1)
return Math.exp(X)
}
function floor(x: LuaType): number {
const X = coerceArgToNumber(x, 'floor', 1)
return Math.floor(X)
}
function fmod(x: LuaType, y: LuaType): number {
const X = coerceArgToNumber(x, 'fmod', 1)
const Y = coerceArgToNumber(y, 'fmod', 2)
return X % Y
}
function frexp(x: LuaType): number[] {
let X = coerceArgToNumber(x, 'frexp', 1)
if (X === 0) {
return [0, 0]
}
const delta = X > 0 ? 1 : -1
X *= delta
const exponent = Math.floor(Math.log(X) / Math.log(2)) + 1
const mantissa = X / Math.pow(2, exponent)
return [mantissa * delta, exponent]
}
function ldexp(m: LuaType, e: LuaType): number {
const M = coerceArgToNumber(m, 'ldexp', 1)
const E = coerceArgToNumber(e, 'ldexp', 2)
return M * Math.pow(2, E)
}
function log(x: LuaType, base?: LuaType): number {
const X = coerceArgToNumber(x, 'log', 1)
if (base === undefined) {
return Math.log(X)
} else {
const B = coerceArgToNumber(base, 'log', 2)
return Math.log(X) / Math.log(B)
}
}
function log10(x: LuaType): number {
const X = coerceArgToNumber(x, 'log10', 1)
// v5.2: warn ('math.log10 is deprecated. Use math.log with 10 as its second argument instead.');
return Math.log(X) / Math.log(10)
}
function max(...args: LuaType[]): number {
const ARGS = args.map((n, i) => coerceArgToNumber(n, 'max', i + 1))
return Math.max(...ARGS)
}
function min(...args: LuaType[]): number {
const ARGS = args.map((n, i) => coerceArgToNumber(n, 'min', i + 1))
return Math.min(...ARGS)
}
function modf(x: LuaType): number[] {
const X = coerceArgToNumber(x, 'modf', 1)
const intValue = Math.floor(X)
const mantissa = X - intValue
return [intValue, mantissa]
}
function pow(x: LuaType, y: LuaType): number {
const X = coerceArgToNumber(x, 'pow', 1)
const Y = coerceArgToNumber(y, 'pow', 2)
return Math.pow(X, Y)
}
function rad(x: LuaType): number {
const X = coerceArgToNumber(x, 'rad', 1)
return (Math.PI / 180) * X
}
function random(min?: LuaType, max?: LuaType): number {
if (min === undefined && max === undefined) return getRandom()
const firstArg = coerceArgToNumber(min, 'random', 1)
const MIN = max === undefined ? firstArg : 1
const MAX = max === undefined ? coerceArgToNumber(max, 'random', 2) : firstArg
if (MIN > MAX) throw new Error("bad argument #2 to 'random' (interval is empty)")
return Math.floor(getRandom() * (MAX - MIN + 1) + MIN)
}
function randomseed(x: LuaType): void {
randomSeed = coerceArgToNumber(x, 'randomseed', 1)
}
function sin(x: LuaType): number {
const X = coerceArgToNumber(x, 'sin', 1)
return Math.sin(X)
}
function sinh(x: LuaType): number {
const X = coerceArgToNumber(x, 'sinh', 1)
return (exp(X) - exp(-X)) / 2
}
function sqrt(x: LuaType): number {
const X = coerceArgToNumber(x, 'sqrt', 1)
return Math.sqrt(X)
}
function tan(x: LuaType): number {
const X = coerceArgToNumber(x, 'tan', 1)
return Math.tan(X)
}
function tanh(x: LuaType): number {
const X = coerceArgToNumber(x, 'tanh', 1)
return (exp(X) - exp(-X)) / (exp(X) + exp(-X))
}
function tointeger(x: LuaType): number {
const X = coerceToNumber(x)
if (X === undefined) return undefined
return Math.floor(X)
}
function type(x: LuaType): string {
const X = coerceToNumber(x)
if (X === undefined) return undefined
if (tointeger(X) === X) return 'integer'
return 'float'
}
function ult(m: LuaType, n: LuaType): boolean {
const M = coerceArgToNumber(m, 'ult', 1)
const N = coerceArgToNumber(n, 'ult', 2)
const toUnsigned = (n: number): number => n >>> 0
return toUnsigned(M) < toUnsigned(N)
}
const libMath = new Table({
abs,
acos,
asin,
atan,
atan2,
ceil,
cos,
cosh,
deg,
exp,
floor,
fmod,
frexp,
huge,
ldexp,
log,
log10,
max,
min,
maxinteger,
mininteger,
modf,
pi,
pow,
rad,
random,
randomseed,
sin,
sinh,
sqrt,
tan,
tanh,
tointeger,
type,
ult
})
export { libMath }

@ -0,0 +1,193 @@
import { Table } from '../Table'
import { LuaType, Config, coerceArgToNumber } from '../utils'
import { LuaError } from '../LuaError'
const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
const MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
// LUA uses strftime internally (https://en.cppreference.com/w/c/chrono/strftime)
type Format =
| '%' // [string] literal %
| 'Y' // [number] year (e.g. 2017)
| 'y' // [number] last 2 digits of year (range [00,99])
| 'b' // [string] abbreviated month name (e.g. Oct)
| 'B' // [string] full month name (e.g. October)
| 'm' // [number] month (range [01,12])
| 'U' // [number] week of the year (Sunday is the first day of the week) (range [00,53])
| 'W' // [number] week of the year (Monday is the first day of the week) (range [00,53])
| 'j' // [number] day of the year (range [001,366])
| 'd' // [number] day of the month (range [01,31])
| 'a' // [string] abbreviated weekday name (e.g. Fri)
| 'A' // [string] full weekday name (e.g. Friday)
| 'w' // [number] weekday - Sunday is 0 (range [0-6])
| 'H' // [number] hour - 24 hour format (range [00-23])
| 'I' // [number] hour - 12 hour format (range [01,12])
| 'M' // [number] minute (range [00,59])
| 'S' // [number] second (range [00,60])
| 'c' // [string] standard date and time string (e.g. Sun Oct 17 04:41:13 2010)
| 'x' // [string] date (e.g. 09/16/98)
| 'X' // [string] time (e.g. 23:48:10)
| 'p' // [string] a.m. or p.m.
| 'Z' // [string] locale-dependent time zone name or abbreviation (e.g. UTC)
const DATE_FORMAT_HANDLERS: Record<Format, (date: Date, utc: boolean) => string> = {
'%': () => '%',
Y: (date, utc) => `${utc ? date.getUTCFullYear() : date.getFullYear()}`,
y: (date, utc) => DATE_FORMAT_HANDLERS.Y(date, utc).substr(-2),
b: (date, utc) => DATE_FORMAT_HANDLERS.B(date, utc).substr(0, 3),
B: (date, utc) => MONTHS[utc ? date.getUTCMonth() : date.getMonth()],
m: (date, utc) => `0${(utc ? date.getUTCMonth() : date.getMonth()) + 1}`.substr(-2),
U: (date, utc) => getWeekOfYear(date, 0, utc),
W: (date, utc) => getWeekOfYear(date, 1, utc),
j: (date, utc) => {
let result = utc ? date.getUTCDate() : date.getDate()
const month = utc ? date.getUTCMonth() : date.getMonth()
const year = utc ? date.getUTCFullYear() : date.getFullYear()
result += DAYS_IN_MONTH.slice(0, month).reduce((sum, n) => sum + n, 0)
if (month > 1 && year % 4 === 0) {
result += 1
}
return `00${result}`.substr(-3)
},
d: (date, utc) => `0${utc ? date.getUTCDate() : date.getDate()}`.substr(-2),
a: (date, utc) => DATE_FORMAT_HANDLERS.A(date, utc).substr(0, 3),
A: (date, utc) => DAYS[utc ? date.getUTCDay() : date.getDay()],
w: (date, utc) => `${utc ? date.getUTCDay() : date.getDay()}`,
H: (date, utc) => `0${utc ? date.getUTCHours() : date.getHours()}`.substr(-2),
I: (date, utc) => `0${(utc ? date.getUTCHours() : date.getHours()) % 12 || 12}`.substr(-2),
M: (date, utc) => `0${utc ? date.getUTCMinutes() : date.getMinutes()}`.substr(-2),
S: (date, utc) => `0${utc ? date.getUTCSeconds() : date.getSeconds()}`.substr(-2),
c: (date, utc) => date.toLocaleString(undefined, utc ? { timeZone: 'UTC' } : undefined),
x: (date, utc) => {
const m = DATE_FORMAT_HANDLERS.m(date, utc)
const d = DATE_FORMAT_HANDLERS.d(date, utc)
const y = DATE_FORMAT_HANDLERS.y(date, utc)
return `${m}/${d}/${y}`
},
X: (date, utc) => {
const h = DATE_FORMAT_HANDLERS.H(date, utc)
const m = DATE_FORMAT_HANDLERS.M(date, utc)
const s = DATE_FORMAT_HANDLERS.S(date, utc)
return `${h}:${m}:${s}`
},
p: (date, utc) => ((utc ? date.getUTCHours() : date.getHours()) < 12 ? 'AM' : 'PM'),
Z: (date, utc) => {
if (utc) return 'UTC'
const match = date.toString().match(/[A-Z][A-Z][A-Z]/)
return match ? match[0] : ''
}
}
function isDST(date: Date): boolean {
const year = date.getFullYear()
const jan = new Date(year, 0)
// ASSUMPTION: If the time offset of the date is the same as it would be in January of the same year, DST is not in effect.
return date.getTimezoneOffset() !== jan.getTimezoneOffset()
}
function getWeekOfYear(date: Date, firstDay: number, utc: boolean): string {
const dayOfYear = parseInt(DATE_FORMAT_HANDLERS.j(date, utc), 10)
const jan1 = new Date(date.getFullYear(), 0, 1, 12)
const offset = (8 - (utc ? jan1.getUTCDay() : jan1.getDay()) + firstDay) % 7
return `0${Math.floor((dayOfYear - offset) / 7) + 1}`.substr(-2)
}
function date(input = '%c', time?: number): string | Table {
const utc = input.substr(0, 1) === '!'
const string = utc ? input.substr(1) : input
const date = new Date()
if (time) {
date.setTime(time * 1000)
}
if (string === '*t') {
return new Table({
year: parseInt(DATE_FORMAT_HANDLERS.Y(date, utc), 10),
month: parseInt(DATE_FORMAT_HANDLERS.m(date, utc), 10),
day: parseInt(DATE_FORMAT_HANDLERS.d(date, utc), 10),
hour: parseInt(DATE_FORMAT_HANDLERS.H(date, utc), 10),
min: parseInt(DATE_FORMAT_HANDLERS.M(date, utc), 10),
sec: parseInt(DATE_FORMAT_HANDLERS.S(date, utc), 10),
wday: parseInt(DATE_FORMAT_HANDLERS.w(date, utc), 10) + 1,
yday: parseInt(DATE_FORMAT_HANDLERS.j(date, utc), 10),
isdst: isDST(date)
})
}
return string.replace(/%[%YybBmUWjdaAwHIMScxXpZ]/g, f => DATE_FORMAT_HANDLERS[f[1] as Format](date, utc))
}
function setlocale(locale = 'C'): string {
if (locale === 'C') return 'C'
// TODO: implement fully
}
function time(table?: Table): number {
let now = Math.round(Date.now() / 1000)
if (!table) return now
const year = table.rawget('year') as number
const month = table.rawget('month') as number
const day = table.rawget('day') as number
const hour = (table.rawget('hour') as number) || 12
const min = table.rawget('min') as number
const sec = table.rawget('sec') as number
// const isdst = table.rawget('isdst') as boolean
if (year) now += year * 31557600
if (month) now += month * 2629800
if (day) now += day * 86400
if (hour) now += hour * 3600
if (min) now += min * 60
if (sec) now += sec
return now
}
function difftime(t2: LuaType, t1: LuaType): number {
const T2 = coerceArgToNumber(t2, 'difftime', 1)
const T1 = coerceArgToNumber(t1, 'difftime', 2)
return T2 - T1
}
const getLibOS = (cfg: Config): Table => {
function exit(code: LuaType): void {
if (!cfg.osExit) throw new LuaError('os.exit requires the config.osExit function')
let CODE = 0
if (typeof code === 'boolean' && code === false) CODE = 1
else if (typeof code === 'number') CODE = code
cfg.osExit(CODE)
}
return new Table({
date,
exit,
setlocale,
time,
difftime
})
}
export { getLibOS }

@ -0,0 +1,102 @@
import { Table } from '../Table'
import { LuaType, Config, coerceArgToString } from '../utils'
import { LuaError } from '../LuaError'
const getLibPackage = (
execModule: (content: string, moduleName: string) => LuaType,
cfg: Config
): {
libPackage: Table
_require: (modname: LuaType) => LuaType
} => {
const LUA_DIRSEP = '/'
const LUA_PATH_SEP = ';'
const LUA_PATH_MARK = '?'
const LUA_EXEC_DIR = '!'
const LUA_IGMARK = '-'
const LUA_PATH = cfg.LUA_PATH
const config = [LUA_DIRSEP, LUA_PATH_SEP, LUA_PATH_MARK, LUA_EXEC_DIR, LUA_IGMARK].join('\n')
const loaded = new Table()
const preload = new Table()
const searchpath = (name: LuaType, path: LuaType, sep?: LuaType, rep?: LuaType): string | [undefined, string] => {
if (!cfg.fileExists) {
throw new LuaError('package.searchpath requires the config.fileExists function')
}
let NAME = coerceArgToString(name, 'searchpath', 1)
const PATH = coerceArgToString(path, 'searchpath', 2)
const SEP = sep === undefined ? '.' : coerceArgToString(sep, 'searchpath', 3)
const REP = rep === undefined ? '/' : coerceArgToString(rep, 'searchpath', 4)
NAME = NAME.replace(SEP, REP)
const paths = PATH.split(';').map(template => template.replace('?', NAME))
for (const path of paths) {
if (cfg.fileExists(path)) return path
}
return [undefined, `The following files don't exist: ${paths.join(' ')}`]
}
const searchers = new Table([
(moduleName: string): [undefined] | [() => LuaType] => {
const res = preload.rawget(moduleName)
if (res === undefined) {
return [undefined]
}
return [res as () => LuaType]
},
(moduleName: string): [undefined] | [string] | [(modname: string, path: string) => LuaType, string] => {
const res = searchpath(moduleName, libPackage.rawget('path'))
if (Array.isArray(res) && res[0] === undefined) {
return [res[1]]
}
if (!cfg.loadFile) {
throw new LuaError('package.searchers requires the config.loadFile function')
}
return [(moduleName: string, path: string) => execModule(cfg.loadFile(path), moduleName), res as string]
}
])
function _require(modname: LuaType): LuaType {
const MODNAME = coerceArgToString(modname, 'require', 1)
const module = loaded.rawget(MODNAME)
if (module) return module
const searcherFns = searchers.numValues.filter(fn => !!fn) as Function[]
for (const searcher of searcherFns) {
const res = searcher(MODNAME)
if (res[0] !== undefined && typeof res[0] !== 'string') {
const loader = res[0]
const result = loader(MODNAME, res[1])
const module = result === undefined ? true : result
loaded.rawset(MODNAME, module)
return module
}
}
throw new LuaError(`Module '${MODNAME}' not found!`)
}
const libPackage = new Table({
path: LUA_PATH,
config,
loaded,
preload,
searchers,
searchpath
})
return { libPackage, _require }
}
export { getLibPackage }

@ -0,0 +1,378 @@
import printj from 'printj'
import { Table, tostring } from '../Table'
import { LuaError } from '../LuaError'
import { posrelat, coerceArgToNumber, coerceArgToString, hasOwnProperty, LuaType } from '../utils'
const ROSETTA_STONE: Record<string, string> = {
'([^a-zA-Z0-9%(])-': '$1*?',
'([^%])-([^a-zA-Z0-9?])': '$1*?$2',
'([^%])\\.': '$1[\\s\\S]',
'(.)-$': '$1*?',
'%a': '[a-zA-Z]',
'%A': '[^a-zA-Z]',
'%c': '[\x00-\x1f]',
'%C': '[^\x00-\x1f]',
'%d': '\\d',
'%D': '[^d]',
'%l': '[a-z]',
'%L': '[^a-z]',
'%p': '[.,"\'?!;:#$%&()*+-/<>=@\\[\\]\\\\^_{}|~]',
'%P': '[^.,"\'?!;:#$%&()*+-/<>=@\\[\\]\\\\^_{}|~]',
'%s': '[ \\t\\n\\f\\v\\r]',
'%S': '[^ \t\n\f\v\r]',
'%u': '[A-Z]',
'%U': '[^A-Z]',
'%w': '[a-zA-Z0-9]',
'%W': '[^a-zA-Z0-9]',
'%x': '[a-fA-F0-9]',
'%X': '[^a-fA-F0-9]',
'%([^a-zA-Z])': '\\$1'
}
function translatePattern(pattern: string): string {
// TODO Add support for balanced character matching (not sure this is easily achieveable).
// Replace single backslash with double backslashes
let tPattern = pattern.replace(/\\/g, '\\\\')
for (const i in ROSETTA_STONE) {
if (hasOwnProperty(ROSETTA_STONE, i)) {
tPattern = tPattern.replace(new RegExp(i, 'g'), ROSETTA_STONE[i])
}
}
let nestingLevel = 0
for (let i = 0, l = tPattern.length; i < l; i++) {
if (i && tPattern.substr(i - 1, 1) === '\\') {
continue
}
// Remove nested square brackets caused by substitutions
const character = tPattern.substr(i, 1)
if (character === '[' || character === ']') {
if (character === ']') {
nestingLevel -= 1
}
if (nestingLevel > 0) {
tPattern = tPattern.substr(0, i) + tPattern.substr(i + 1)
i -= 1
l -= 1
}
if (character === '[') {
nestingLevel += 1
}
}
}
return tPattern
}
/**
* Returns the internal numeric codes of the characters s[i], s[i+1], ..., s[j].
* The default value for i is 1; the default value for j is i.
* These indices are corrected following the same rules of function string.sub.
*
* Numeric codes are not necessarily portable across platforms.
*/
function byte(s: LuaType, i: LuaType, j: LuaType): number[] {
const S = coerceArgToString(s, 'byte', 1)
const I = i === undefined ? 1 : coerceArgToNumber(i, 'byte', 2)
const J = j === undefined ? I : coerceArgToNumber(j, 'byte', 3)
return S.substring(I - 1, J)
.split('')
.map(c => c.charCodeAt(0))
}
/**
* Receives zero or more integers. Returns a string with length equal to the number of arguments,
* in which each character has the internal numeric code equal to its corresponding argument.
*
* Numeric codes are not necessarily portable across platforms.
*/
function char(...bytes: LuaType[]): string {
return bytes
.map((b, i) => {
const B = coerceArgToNumber(b, 'char', i)
return String.fromCharCode(B)
})
.join('')
}
/**
* Looks for the first match of pattern (see §6.4.1) in the string s. If it finds a match, then find returns
* the indices of s where this occurrence starts and ends; otherwise, it returns nil.
* A third, optional numeric argument init specifies where to start the search; its default value is 1 and can be negative.
* A value of true as a fourth, optional argument plain turns off the pattern matching facilities,
* so the function does a plain "find substring" operation, with no characters in pattern being considered magic.
* Note that if plain is given, then init must be given as well.
*
* If the pattern has captures, then in a successful match the captured values are also returned, after the two indices.
*/
function find(s: LuaType, pattern: LuaType, init: LuaType, plain: LuaType): (number | string)[] {
const S = coerceArgToString(s, 'find', 1)
const P = coerceArgToString(pattern, 'find', 2)
const INIT = init === undefined ? 1 : coerceArgToNumber(init, 'find', 3)
const PLAIN = plain === undefined ? false : coerceArgToNumber(plain, 'find', 4)
// Regex
if (!PLAIN) {
const regex = new RegExp(translatePattern(P))
const index = S.substr(INIT - 1).search(regex)
if (index < 0) return
const match = S.substr(INIT - 1).match(regex)
const result = [index + INIT, index + INIT + match[0].length - 1]
match.shift()
return [...result, ...match]
}
// Plain
const index = S.indexOf(P, INIT - 1)
return index === -1 ? undefined : [index + 1, index + P.length]
}
function format(formatstring: string, ...args: LuaType[]): string {
// Pattern with all constraints:
// /%%|%([-+ #0]{0,5})?(\d{0,2})?(?:\.(\d{0,2}))?([AEGXacdefgioqsux])/g
const PATTERN = /%%|%([-+ #0]*)?(\d*)?(?:\.(\d*))?(.)/g
let i = -1
return formatstring.replace(PATTERN, (format, flags, width, precision, modifier) => {
if (format === '%%') return '%'
if (!modifier.match(/[AEGXacdefgioqsux]/)) {
throw new LuaError(`invalid option '%${format}' to 'format'`)
}
if (flags && flags.length > 5) {
throw new LuaError(`invalid format (repeated flags)`)
}
if (width && width.length > 2) {
throw new LuaError(`invalid format (width too long)`)
}
if (precision && precision.length > 2) {
throw new LuaError(`invalid format (precision too long)`)
}
i += 1
const arg = args[i]
if (arg === undefined) {
throw new LuaError(`bad argument #${i} to 'format' (no value)`)
}
if (/A|a|E|e|f|G|g/.test(modifier)) {
return printj.sprintf(format, coerceArgToNumber(arg, 'format', i))
}
if (/c|d|i|o|u|X|x/.test(modifier)) {
return printj.sprintf(format, coerceArgToNumber(arg, 'format', i))
}
if (modifier === 'q') {
return `"${(arg as string).replace(/([\n"])/g, '\\$1')}"`
}
if (modifier === 's') {
return printj.sprintf(format, tostring(arg))
}
return printj.sprintf(format, arg)
})
}
/**
* Returns an iterator function that, each time it is called, returns the next captures from pattern (see §6.4.1)
* over the string s. If pattern specifies no captures, then the whole match is produced in each call.
*/
function gmatch(s: LuaType, pattern: LuaType): () => string[] {
const S = coerceArgToString(s, 'gmatch', 1)
const P = translatePattern(coerceArgToString(pattern, 'gmatch', 2))
const reg = new RegExp(P, 'g')
const matches = S.match(reg)
return () => {
const match = matches.shift()
if (match === undefined) return []
const groups = new RegExp(P).exec(match)
groups.shift()
return groups.length ? groups : [match]
}
}
/**
* Returns a copy of s in which all (or the first n, if given) occurrences of the pattern (see §6.4.1)
* have been replaced by a replacement string specified by repl, which can be a string, a table, or a function.
* gsub also returns, as its second value, the total number of matches that occurred.
* The name gsub comes from Global SUBstitution.
*
* If repl is a string, then its value is used for replacement. The character % works as an escape character:
* any sequence in repl of the form %d, with d between 1 and 9, stands for the value of the d-th captured substring.
* The sequence %0 stands for the whole match. The sequence %% stands for a single %.
*
* If repl is a table, then the table is queried for every match, using the first capture as the key.
*
* If repl is a function, then this function is called every time a match occurs,
* with all captured substrings passed as arguments, in order.
*
* In any case, if the pattern specifies no captures, then it behaves as if the whole pattern was inside a capture.
*
* If the value returned by the table query or by the function call is a string or a number,
* then it is used as the replacement string; otherwise, if it is false or nil, then there is no replacement
* (that is, the original match is kept in the string).
*/
function gsub(s: LuaType, pattern: LuaType, repl: LuaType, n?: LuaType): string {
let S = coerceArgToString(s, 'gsub', 1)
const N = n === undefined ? Infinity : coerceArgToNumber(n, 'gsub', 3)
const P = translatePattern(coerceArgToString(pattern, 'gsub', 2))
const REPL = ((): ((strs: string[]) => string) => {
if (typeof repl === 'function')
return strs => {
const ret = repl(strs[0])[0]
return ret === undefined ? strs[0] : ret
}
if (repl instanceof Table) return strs => repl.get(strs[0]).toString()
return strs => `${repl}`.replace(/%([0-9])/g, (_, i) => strs[i])
})()
let result = ''
let count = 0
let match
let lastMatch
while (count < N && S && (match = S.match(P))) {
const prefix =
// eslint-disable-next-line no-nested-ternary
match[0].length > 0 ? S.substr(0, match.index) : lastMatch === undefined ? '' : S.substr(0, 1)
lastMatch = match[0]
result += `${prefix}${REPL(match)}`
S = S.substr(`${prefix}${lastMatch}`.length)
count += 1
}
return `${result}${S}`
}
/**
* Receives a string and returns its length. The empty string "" has length 0.
* Embedded zeros are counted, so "a\000bc\000" has length 5.
*/
function len(s: LuaType): number {
const str = coerceArgToString(s, 'len', 1)
return str.length
}
/**
* Receives a string and returns a copy of this string with all uppercase letters changed to lowercase.
* All other characters are left unchanged.
* The definition of what an uppercase letter is depends on the current locale.
*/
function lower(s: LuaType): string {
const str = coerceArgToString(s, 'lower', 1)
return str.toLowerCase()
}
/**
* Looks for the first match of pattern (see §6.4.1) in the string s.
* If it finds one, then match returns the captures from the pattern; otherwise it returns nil.
* If pattern specifies no captures, then the whole match is returned.
* A third, optional numeric argument init specifies where to start the search; its default value is 1 and can be negative.
*/
function match(s: LuaType, pattern: LuaType, init: LuaType = 0): string | RegExpMatchArray {
let str = coerceArgToString(s, 'match', 1)
const patt = coerceArgToString(pattern, 'match', 2)
const ini = coerceArgToNumber(init, 'match', 3)
str = str.substr(ini)
const matches = str.match(new RegExp(translatePattern(patt)))
if (!matches) {
return
} else if (!matches[1]) {
return matches[0]
}
matches.shift()
return matches
}
/**
* Returns a string that is the concatenation of n copies of the string s separated by the string sep.
* The default value for sep is the empty string (that is, no separator).
* Returns the empty string if n is not positive.
*/
function rep(s: LuaType, n: LuaType, sep?: LuaType): string {
const str = coerceArgToString(s, 'rep', 1)
const num = coerceArgToNumber(n, 'rep', 2)
const SEP = sep === undefined ? '' : coerceArgToString(sep, 'rep', 3)
return Array(num)
.fill(str)
.join(SEP)
}
/** Returns a string that is the string s reversed. */
function reverse(s: LuaType): string {
const str = coerceArgToString(s, 'reverse', 1)
return str
.split('')
.reverse()
.join('')
}
/**
* Returns the substring of s that starts at i and continues until j; i and j can be negative.
* If j is absent, then it is assumed to be equal to -1 (which is the same as the string length).
* In particular, the call string.sub(s,1,j) returns a prefix of s with length j, and string.sub(s, -i)
* (for a positive j) returns a suffix of s with length i.
*
* If, after the translation of negative indices, i is less than 1, it is corrected to 1. If j is greater than
* the string length, it is corrected to that length. If, after these corrections, i is greater than j,
* the function returns the empty string.
*/
function sub(s: LuaType, i: LuaType = 1, j: LuaType = -1): string {
const S = coerceArgToString(s, 'sub', 1)
let start = posrelat(coerceArgToNumber(i, 'sub', 2), S.length)
let end = posrelat(coerceArgToNumber(j, 'sub', 3), S.length)
if (start < 1) start = 1
if (end > S.length) end = S.length
if (start <= end) return S.substr(start - 1, end - start + 1)
return ''
}
/**
* Receives a string and returns a copy of this string with all lowercase letters changed to uppercase.
* All other characters are left unchanged.
* The definition of what a lowercase letter is depends on the current locale.
*/
function upper(s: LuaType): string {
const S = coerceArgToString(s, 'upper', 1)
return S.toUpperCase()
}
const libString = new Table({
byte,
char,
find,
format,
gmatch,
gsub,
len,
lower,
match,
rep,
reverse,
sub,
upper
})
const metatable = new Table({ __index: libString })
export { libString, metatable }

@ -0,0 +1,182 @@
import { Table, getn } from '../Table'
import {
LuaType,
coerceToBoolean,
coerceArgToNumber,
coerceArgToString,
coerceArgToTable,
coerceArgToFunction
} from '../utils'
import { LuaError } from '../LuaError'
/**
* Given a list where all elements are strings or numbers, returns the string list[i]..sep..list[i+1] ··· sep..list[j].
* The default value for sep is the empty string, the default for i is 1, and the default for j is #list.
* If i is greater than j, returns the empty string.
*/
function concat(table: LuaType, sep: LuaType = '', i: LuaType = 1, j?: LuaType): string {
const TABLE = coerceArgToTable(table, 'concat', 1)
const SEP = coerceArgToString(sep, 'concat', 2)
const I = coerceArgToNumber(i, 'concat', 3)
const J = j === undefined ? maxn(TABLE) : coerceArgToNumber(j, 'concat', 4)
return []
.concat(TABLE.numValues)
.splice(I, J - I + 1)
.join(SEP)
}
/**
* Inserts element value at position pos in list, shifting up the elements list[pos], list[pos+1], ···, list[#list].
* The default value for pos is #list+1, so that a call table.insert(t,x) inserts x at the end of list t.
*/
function insert(table: LuaType, pos: LuaType, value?: LuaType): void {
const TABLE = coerceArgToTable(table, 'insert', 1)
const POS = value === undefined ? TABLE.numValues.length : coerceArgToNumber(pos, 'insert', 2)
const VALUE = value === undefined ? pos : value
TABLE.numValues.splice(POS, 0, undefined)
TABLE.set(POS, VALUE)
}
function maxn(table: LuaType): number {
const TABLE = coerceArgToTable(table, 'maxn', 1)
return TABLE.numValues.length - 1
}
/**
* Moves elements from table a1 to table a2, performing the equivalent to the following multiple assignment:
*
* `a2[t],··· = a1[f],···,a1[e].`
*
* The default for a2 is a1.
* The destination range can overlap with the source range.
* The number of elements to be moved must fit in a Lua integer.
*
* Returns the destination table a2.
*/
function move(a1: LuaType, f: LuaType, e: LuaType, t: LuaType, a2?: LuaType): Table {
const A1 = coerceArgToTable(a1, 'move', 1)
const F = coerceArgToNumber(f, 'move', 2)
const E = coerceArgToNumber(e, 'move', 3)
const T = coerceArgToNumber(t, 'move', 4)
const A2 = a2 === undefined ? A1 : coerceArgToTable(a2, 'move', 5)
if (E >= F) {
if (F <= 0 && E >= Number.MAX_SAFE_INTEGER + F) throw new LuaError('too many elements to move')
const n = E - F + 1 // number of elements to movea
if (T > Number.MAX_SAFE_INTEGER - n + 1) throw new LuaError('destination wrap around')
if (T > E || T <= F || A2 !== A1) {
for (let i = 0; i < n; i++) {
const v = A1.get(F + i)
A2.set(T + i, v)
}
} else {
for (let i = n - 1; i >= 0; i--) {
const v = A1.get(F + i)
A2.set(T + i, v)
}
}
}
return A2
}
/**
* Returns a new table with all arguments stored into keys 1, 2, etc. and with a field "n" with the total number of arguments.
* Note that the resulting table may not be a sequence.
*/
function pack(...args: LuaType[]): Table {
const table = new Table(args)
table.rawset('n', args.length)
return table
}
/**
* Removes from list the element at position pos, returning the value of the removed element.
* When pos is an integer between 1 and #list, it shifts down the elements list[pos+1], list[pos+2], ···, list[#list] and
* erases element list[#list]; The index pos can also be 0 when #list is 0, or #list + 1;
* in those cases, the function erases the element list[pos].
*
* The default value for pos is #list, so that a call table.remove(l) removes the last element of list l.
*/
function remove(table: LuaType, pos?: LuaType): LuaType {
const TABLE = coerceArgToTable(table, 'remove', 1)
const max = getn(TABLE)
const POS = pos === undefined ? max : coerceArgToNumber(pos, 'remove', 2)
if (POS > max || POS < 0) {
return
}
const vals = TABLE.numValues
const result = vals.splice(POS, 1)[0]
let i = POS
while (i < max && vals[i] === undefined) {
delete vals[i]
i += 1
}
return result
}
/**
* Sorts list elements in a given order, in-place, from list[1] to list[#list].
* If comp is given, then it must be a function that receives two list elements and
* returns true when the first element must come before the second in the final order
* (so that, after the sort, i < j implies not comp(list[j],list[i])).
* If comp is not given, then the standard Lua operator < is used instead.
*
* Note that the comp function must define a strict partial order over the elements in the list;
* that is, it must be asymmetric and transitive. Otherwise, no valid sort may be possible.
*
* The sort algorithm is not stable: elements considered equal by the given order may have
* their relative positions changed by the sort.
*/
function sort(table: Table, comp?: Function): void {
const TABLE = coerceArgToTable(table, 'sort', 1)
let sortFunc: (a: LuaType, b: LuaType) => number
if (comp) {
const COMP = coerceArgToFunction(comp, 'sort', 2)
sortFunc = (a, b) => (coerceToBoolean(COMP(a, b)[0]) ? -1 : 1)
} else {
sortFunc = (a, b) => (a < b ? -1 : 1)
}
const arr = TABLE.numValues
arr.shift()
arr.sort(sortFunc).unshift(undefined)
}
/**
* Returns the elements from the given list. This function is equivalent to
*
* `return list[i], list[i+1], ···, list[j]`
*
* By default, i is 1 and j is #list.
*/
function unpack(table: LuaType, i?: LuaType, j?: LuaType): LuaType[] {
const TABLE = coerceArgToTable(table, 'unpack', 1)
const I = i === undefined ? 1 : coerceArgToNumber(i, 'unpack', 2)
const J = j === undefined ? getn(TABLE) : coerceArgToNumber(j, 'unpack', 3)
return TABLE.numValues.slice(I, J + 1)
}
const libTable = new Table({
getn,
concat,
insert,
maxn,
move,
pack,
remove,
sort,
unpack
})
export { libTable }

@ -0,0 +1,184 @@
import { MetaMethods, Table, getn } from './Table'
import { coerceToNumber, coerceToString, LuaType, coerceToBoolean } from './utils'
import { LuaError } from './LuaError'
const binaryArithmetic = <R extends boolean | number>(
left: LuaType,
right: LuaType,
metaMethodName: MetaMethods,
callback: (l: number, r: number) => R
): R => {
const mm =
(left instanceof Table && left.getMetaMethod(metaMethodName)) ||
(right instanceof Table && right.getMetaMethod(metaMethodName))
if (mm) return mm(left, right)[0]
const L = coerceToNumber(left, 'attempt to perform arithmetic on a %type value')
const R = coerceToNumber(right, 'attempt to perform arithmetic on a %type value')
return callback(L, R)
}
const binaryBooleanArithmetic = (
left: LuaType,
right: LuaType,
metaMethodName: MetaMethods,
callback: (l: LuaType, r: LuaType) => boolean
): boolean => {
if (
(typeof left === 'string' && typeof right === 'string') ||
(typeof left === 'number' && typeof right === 'number')
) {
return callback(left, right)
}
return binaryArithmetic<boolean>(left, right, metaMethodName, callback)
}
// extra
const bool = (value: LuaType): boolean => coerceToBoolean(value)
// unary
const not = (value: LuaType): boolean => !bool(value)
const unm = (value: LuaType): number => {
const mm = value instanceof Table && value.getMetaMethod('__unm')
if (mm) return mm(value)[0]
return -1 * coerceToNumber(value, 'attempt to perform arithmetic on a %type value')
}
const bnot = (value: LuaType): number => {
const mm = value instanceof Table && value.getMetaMethod('__bnot')
if (mm) return mm(value)[0]
return ~coerceToNumber(value, 'attempt to perform arithmetic on a %type value')
}
const len = (value: LuaType): number => {
if (value instanceof Table) {
const mm = value.getMetaMethod('__len')
if (mm) return mm(value)[0]
return getn(value)
}
if (typeof value === 'string') return value.length
throw new LuaError('attempt to get length of an unsupported value')
// if (typeof value === 'object') {
// let length = 0
// for (const key in value) {
// if (hasOwnProperty(value, key)) {
// length += 1
// }
// }
// return length
// }
}
// binary
const add = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__add', (l, r) => l + r)
const sub = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__sub', (l, r) => l - r)
const mul = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__mul', (l, r) => l * r)
const mod = (left: LuaType, right: LuaType): number =>
binaryArithmetic(left, right, '__mod', (l, r) => {
if (r === 0 || r === -Infinity || r === Infinity || isNaN(l) || isNaN(r)) return NaN
const absR = Math.abs(r)
let result = Math.abs(l) % absR
if (l * r < 0) result = absR - result
if (r < 0) result *= -1
return result
})
const pow = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__pow', Math.pow)
const div = (left: LuaType, right: LuaType): number =>
binaryArithmetic(left, right, '__div', (l, r) => {
if (r === undefined) throw new LuaError('attempt to perform arithmetic on a nil value')
return l / r
})
const idiv = (left: LuaType, right: LuaType): number =>
binaryArithmetic(left, right, '__idiv', (l, r) => {
if (r === undefined) throw new LuaError('attempt to perform arithmetic on a nil value')
return Math.floor(l / r)
})
const band = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__band', (l, r) => l & r)
const bor = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__bor', (l, r) => l | r)
const bxor = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__bxor', (l, r) => l ^ r)
const shl = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__shl', (l, r) => l << r)
const shr = (left: LuaType, right: LuaType): number => binaryArithmetic(left, right, '__shr', (l, r) => l >> r)
const concat = (left: LuaType, right: LuaType): string => {
const mm =
(left instanceof Table && left.getMetaMethod('__concat')) ||
(right instanceof Table && right.getMetaMethod('__concat'))
if (mm) return mm(left, right)[0]
const L = coerceToString(left, 'attempt to concatenate a %type value')
const R = coerceToString(right, 'attempt to concatenate a %type value')
return `${L}${R}`
}
const neq = (left: LuaType, right: LuaType): boolean => !eq(left, right)
const eq = (left: LuaType, right: LuaType): boolean => {
const mm =
right !== left &&
left instanceof Table &&
right instanceof Table &&
left.metatable === right.metatable &&
left.getMetaMethod('__eq')
if (mm) return !!mm(left, right)[0]
return left === right
}
const lt = (left: LuaType, right: LuaType): boolean => binaryBooleanArithmetic(left, right, '__lt', (l, r) => l < r)
const le = (left: LuaType, right: LuaType): boolean => binaryBooleanArithmetic(left, right, '__le', (l, r) => l <= r)
const gt = (left: LuaType, right: LuaType): boolean => !le(left, right)
const ge = (left: LuaType, right: LuaType): boolean => !lt(left, right)
const operators = {
bool,
not,
unm,
bnot,
len,
add,
sub,
mul,
mod,
pow,
div,
idiv,
band,
bor,
bxor,
shl,
shr,
concat,
neq,
eq,
lt,
le,
gt,
ge
}
export { operators }

@ -0,0 +1,752 @@
import luaparse from 'luaparse'
type Block =
| luaparse.IfClause
| luaparse.ElseifClause
| luaparse.ElseClause
| luaparse.WhileStatement
| luaparse.DoStatement
| luaparse.RepeatStatement
| luaparse.FunctionDeclaration
| luaparse.ForNumericStatement
| luaparse.ForGenericStatement
| luaparse.Chunk
const isBlock = (node: luaparse.Node): node is Block =>
node.type === 'IfClause' ||
node.type === 'ElseifClause' ||
node.type === 'ElseClause' ||
node.type === 'WhileStatement' ||
node.type === 'DoStatement' ||
node.type === 'RepeatStatement' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ForNumericStatement' ||
node.type === 'ForGenericStatement' ||
node.type === 'Chunk'
class MemExpr extends String {
public base: string | MemExpr
public property: string | MemExpr
public constructor(base: string | MemExpr, property: string | MemExpr) {
super()
this.base = base
this.property = property
}
public get(): string {
return `__lua.get(${this.base}, ${this.property})`
}
public set(value: string | MemExpr): string {
return `${this.base}.set(${this.property}, ${value})`
}
public setFn(): string {
return `${this.base}.setFn(${this.property})`
}
public toString(): string {
return this.get()
}
public valueOf(): string {
return this.get()
}
}
const UNI_OP_MAP = {
not: 'not',
'-': 'unm',
'~': 'bnot',
'#': 'len'
}
const BIN_OP_MAP = {
'+': 'add',
'-': 'sub',
'*': 'mul',
'%': 'mod',
'^': 'pow',
'/': 'div',
'//': 'idiv',
'&': 'band',
'|': 'bor',
'~': 'bxor',
'<<': 'shl',
'>>': 'shr',
'..': 'concat',
'~=': 'neq',
'==': 'eq',
'<': 'lt',
'<=': 'le',
'>': 'gt',
'>=': 'ge'
}
const generate = (node: luaparse.Node): string | MemExpr => {
switch (node.type) {
case 'LabelStatement': {
return `case '${node.label.name}': label = undefined`
}
case 'BreakStatement': {
return 'break'
}
case 'GotoStatement': {
return `label = '${node.label.name}'; continue`
}
case 'ReturnStatement': {
const args = parseExpressions(node.arguments)
return `return ${args}`
}
case 'IfStatement': {
const clauses = node.clauses.map(clause => generate(clause))
return clauses.join(' else ')
}
case 'IfClause':
case 'ElseifClause': {
const condition = expression(node.condition)
const body = parseBody(node)
return `if (__lua.bool(${condition})) {\n${body}\n}`
}
case 'ElseClause': {
const body = parseBody(node)
return `{\n${body}\n}`
}
case 'WhileStatement': {
const condition = expression(node.condition)
const body = parseBody(node)
return `while(${condition}) {\n${body}\n}`
}
case 'DoStatement': {
const body = parseBody(node)
return `\n${body}\n`
}
case 'RepeatStatement': {
const condition = expression(node.condition)
const body = parseBody(node)
return `do {\n${body}\n} while (!(${condition}))`
}
case 'LocalStatement': {
return parseAssignments(node)
}
case 'AssignmentStatement': {
return parseAssignments(node)
}
case 'CallStatement': {
return generate(node.expression)
}
case 'FunctionDeclaration': {
const getFuncDef = (params: string[]): string => {
const paramStr = params.join(';\n')
const body = parseBody(node, paramStr)
const argsStr = params.length === 0 ? '' : '...args'
const returnStr =
node.body.findIndex(node => node.type === 'ReturnStatement') === -1 ? '\nreturn []' : ''
return `(${argsStr}) => {\n${body}${returnStr}\n}`
}
const params = node.parameters.map(param => {
if (param.type === 'VarargLiteral') {
return `$${nodeToScope.get(param)}.setVarargs(args)`
}
return `$${nodeToScope.get(param)}.setLocal('${param.name}', args.shift())`
})
// anonymous function
if (node.identifier === null) return getFuncDef(params)
if (node.identifier.type === 'Identifier') {
const scope = nodeToScope.get(node.identifier)
const setStr = node.isLocal ? 'setLocal' : 'set'
return `$${scope}.${setStr}('${node.identifier.name}', ${getFuncDef(params)})`
}
const identifier = generate(node.identifier) as MemExpr
if (node.identifier.indexer === ':') {
params.unshift(`$${nodeToScope.get(node)}.setLocal('self', args.shift())`)
}
return identifier.set(getFuncDef(params))
}
case 'ForNumericStatement': {
const varName = node.variable.name
const start = expression(node.start)
const end = expression(node.end)
const step = node.step === null ? 1 : expression(node.step)
const init = `let ${varName} = ${start}, end = ${end}, step = ${step}`
const cond = `step > 0 ? ${varName} <= end : ${varName} >= end`
const after = `${varName} += step`
const varInit = `$${nodeToScope.get(node.variable)}.setLocal('${varName}', ${varName});`
const body = parseBody(node, varInit)
return `for (${init}; ${cond}; ${after}) {\n${body}\n}`
}
case 'ForGenericStatement': {
const iterators = parseExpressions(node.iterators)
const variables = node.variables
.map((variable, index) => {
return `$${nodeToScope.get(variable)}.setLocal('${variable.name}', res[${index}])`
})
.join(';\n')
const body = parseBody(node, variables)
return `for (let [iterator, table, next] = ${iterators}, res = __lua.call(iterator, table, next); res[0] !== undefined; res = __lua.call(iterator, table, res[0])) {\n${body}\n}`
}
case 'Chunk': {
const body = parseBody(node)
return `'use strict'\nconst $0 = __lua.globalScope\nlet vars\nlet vals\nlet label\n\n${body}`
}
case 'Identifier': {
return `$${nodeToScope.get(node)}.get('${node.name}')`
}
case 'StringLiteral': {
const S = node.value
.replace(/([^\\])?\\(\d{1,3})/g, (_, pre, dec) => `${pre || ''}${String.fromCharCode(dec)}`)
.replace(/\\/g, '\\\\')
return `\`${S}\``
}
case 'NumericLiteral': {
return node.value.toString()
}
case 'BooleanLiteral': {
return node.value ? 'true' : 'false'
}
case 'NilLiteral': {
return 'undefined'
}
case 'VarargLiteral': {
return `$${nodeToScope.get(node)}.getVarargs()`
}
// inside TableConstructorExpression
// case 'TableKey': {}
// case 'TableKeyString': {}
// case 'TableValue': {}
case 'TableConstructorExpression': {
if (node.fields.length === 0) return 'new __lua.Table()'
const fields = node.fields
.map((field, index, arr) => {
if (field.type === 'TableKey') {
return `t.rawset(${generate(field.key)}, ${expression(field.value)})`
}
if (field.type === 'TableKeyString') {
return `t.rawset('${field.key.name}', ${expression(field.value)})`
}
if (field.type === 'TableValue') {
if (index === arr.length - 1 && ExpressionReturnsArray(field.value)) {
return `t.insert(...${generate(field.value)})`
}
return `t.insert(${expression(field.value)})`
}
})
.join(';\n')
return `new __lua.Table(t => {\n${fields}\n})`
}
case 'UnaryExpression': {
const operator = UNI_OP_MAP[node.operator]
const argument = expression(node.argument)
if (!operator) {
throw new Error(`Unhandled unary operator: ${node.operator}`)
}
return `__lua.${operator}(${argument})`
}
case 'BinaryExpression': {
const left = expression(node.left)
const right = expression(node.right)
const operator = BIN_OP_MAP[node.operator]
if (!operator) {
throw new Error(`Unhandled binary operator: ${node.operator}`)
}
return `__lua.${operator}(${left}, ${right})`
}
case 'LogicalExpression': {
const left = expression(node.left)
const right = expression(node.right)
const operator = node.operator
if (operator === 'and') {
return `(!__lua.bool(${left})?${left}:${right})`
}
if (operator === 'or') {
return `(__lua.bool(${left})?${left}:${right})`
}
throw new Error(`Unhandled logical operator: ${node.operator}`)
}
case 'MemberExpression': {
const base = expression(node.base)
return new MemExpr(base, `'${node.identifier.name}'`)
}
case 'IndexExpression': {
const base = expression(node.base)
const index = expression(node.index)
return new MemExpr(base, index)
}
case 'CallExpression':
case 'TableCallExpression':
case 'StringCallExpression': {
const functionName = expression(node.base)
const args =
node.type === 'CallExpression'
? parseExpressionList(node.arguments).join(', ')
: expression(node.type === 'TableCallExpression' ? node.arguments : node.argument)
if (functionName instanceof MemExpr && node.base.type === 'MemberExpression' && node.base.indexer === ':') {
return `__lua.call(${functionName}, ${functionName.base}, ${args})`
}
return `__lua.call(${functionName}, ${args})`
}
default:
throw new Error(`No generator found for: ${node.type}`)
}
}
const parseBody = (node: Block, header = ''): string => {
const scope = nodeToScope.get(node)
const scopeDef = scope === undefined ? '' : `const $${scope} = $${scopeToParentScope.get(scope)}.extend();`
const body = node.body.map(statement => generate(statement)).join(';\n')
const goto = nodeToGoto.get(node)
if (goto === undefined) return `${scopeDef}\n${header}\n${body}`
const gotoHeader = `L${goto}: do { switch(label) { case undefined:`
const gotoParent = gotoToParentGoto.get(goto)
const def = gotoParent === undefined ? '' : `break; default: continue L${gotoParent}\n`
const footer = `${def}} } while (label)`
return `${scopeDef}\n${gotoHeader}\n${header}\n${body}\n${footer}`
}
const expression = (node: luaparse.Expression): string | MemExpr => {
const v = generate(node)
if (ExpressionReturnsArray(node)) return `${v}[0]`
return v
}
const parseExpressions = (expressions: luaparse.Expression[]): string | MemExpr => {
// return the `array` directly instead of `[...array]`
if (expressions.length === 1 && ExpressionReturnsArray(expressions[0])) {
return generate(expressions[0])
}
return `[${parseExpressionList(expressions).join(', ')}]`
}
const parseExpressionList = (expressions: luaparse.Expression[]): (string | MemExpr)[] => {
return expressions.map((node, index, arr) => {
const value = generate(node)
if (ExpressionReturnsArray(node)) {
return index === arr.length - 1 ? `...${value}` : `${value}[0]`
}
return value
})
}
const parseAssignments = (node: luaparse.LocalStatement | luaparse.AssignmentStatement): string => {
const lines: (string | MemExpr)[] = []
const valFns: string[] = []
const useTempVar = node.variables.length > 1 && node.init.length > 0 && !node.init.every(isLiteral)
for (let i = 0; i < node.variables.length; i++) {
const K = node.variables[i]
const V = node.init[i]
const initStr =
// eslint-disable-next-line no-nested-ternary
useTempVar ? `vars[${i}]` : V === undefined ? 'undefined' : expression(V)
if (K.type === 'Identifier') {
const setStr = node.type === 'LocalStatement' ? 'setLocal' : 'set'
lines.push(`$${nodeToScope.get(K)}.${setStr}('${K.name}', ${initStr})`)
} else {
const name = generate(K) as MemExpr
if (useTempVar) {
lines.push(`vals[${valFns.length}](${initStr})`)
valFns.push(name.setFn())
} else {
lines.push(name.set(initStr))
}
}
}
// push remaining CallExpressions
for (let i = node.variables.length; i < node.init.length; i++) {
const init = node.init[i]
if (isCallExpression(init)) {
lines.push(generate(init))
}
}
if (useTempVar) {
lines.unshift(`vars = ${parseExpressions(node.init)}`)
if (valFns.length > 0) {
lines.unshift(`vals = [${valFns.join(', ')}]`)
}
}
return lines.join(';\n')
}
const isCallExpression = (
node: luaparse.Expression
): node is luaparse.CallExpression | luaparse.StringCallExpression | luaparse.TableCallExpression => {
return node.type === 'CallExpression' || node.type === 'StringCallExpression' || node.type === 'TableCallExpression'
}
const ExpressionReturnsArray = (node: luaparse.Expression): boolean => {
return isCallExpression(node) || node.type === 'VarargLiteral'
}
const isLiteral = (node: luaparse.Expression): boolean => {
return (
node.type === 'StringLiteral' ||
node.type === 'NumericLiteral' ||
node.type === 'BooleanLiteral' ||
node.type === 'NilLiteral' ||
node.type === 'TableConstructorExpression'
)
}
const checkGoto = (ast: luaparse.Chunk): void => {
const gotoInfo: {
type: 'local' | 'label' | 'goto'
name: string
scope: number
last?: boolean
}[] = []
let gotoScope = 0
const gotoScopeMap = new Map<number, number>()
const getNextGotoScope = (() => {
let id = 0
return () => {
id += 1
return id
}
})()
const check = (node: luaparse.Node): void => {
if (isBlock(node)) {
createGotoScope()
for (let i = 0; i < node.body.length; i++) {
const n = node.body[i]
switch (n.type) {
case 'LocalStatement': {
gotoInfo.push({
type: 'local',
name: n.variables[0].name,
scope: gotoScope
})
break
}
case 'LabelStatement': {
if (
gotoInfo.find(
node => node.type === 'label' && node.name === n.label.name && node.scope === gotoScope
)
) {
throw new Error(`label '${n.label.name}' already defined`)
}
gotoInfo.push({
type: 'label',
name: n.label.name,
scope: gotoScope,
last:
node.type !== 'RepeatStatement' &&
node.body.slice(i).every(n => n.type === 'LabelStatement')
})
break
}
case 'GotoStatement': {
gotoInfo.push({
type: 'goto',
name: n.label.name,
scope: gotoScope
})
break
}
case 'IfStatement': {
n.clauses.forEach(n => check(n))
break
}
default: {
check(n)
}
}
}
destroyGotoScope()
}
}
check(ast)
function createGotoScope(): void {
const parent = gotoScope
gotoScope = getNextGotoScope()
gotoScopeMap.set(gotoScope, parent)
}
function destroyGotoScope(): void {
gotoScope = gotoScopeMap.get(gotoScope)
}
for (let i = 0; i < gotoInfo.length; i++) {
const goto = gotoInfo[i]
if (goto.type === 'goto') {
const label = gotoInfo
.filter(node => node.type === 'label' && node.name === goto.name && node.scope <= goto.scope)
.sort((a, b) => Math.abs(goto.scope - a.scope) - Math.abs(goto.scope - b.scope))[0]
if (!label) {
throw new Error(`no visible label '${goto.name}' for <goto>`)
}
const labelI = gotoInfo.findIndex(n => n === label)
if (labelI > i) {
const locals = gotoInfo
.slice(i, labelI)
.filter(node => node.type === 'local' && node.scope === label.scope)
if (!label.last && locals.length > 0) {
throw new Error(`<goto ${goto.name}> jumps into the scope of local '${locals[0].name}'`)
}
}
}
}
}
const visitNode = (
node: luaparse.Node,
visitProp: (node: luaparse.Node, prevScope: number, prevGoto: number) => void,
nextScope: number,
isNewScope: boolean,
nextGoto: number
): void => {
const VP = (node: luaparse.Node | luaparse.Node[], partOfBlock = true): void => {
if (!node) return
const S = partOfBlock === false && isNewScope ? scopeToParentScope.get(nextScope) : nextScope
if (Array.isArray(node)) {
node.forEach(n => visitProp(n, S, nextGoto))
} else {
visitProp(node, S, nextGoto)
}
}
switch (node.type) {
case 'LocalStatement':
case 'AssignmentStatement':
VP(node.variables)
VP(node.init)
break
case 'UnaryExpression':
VP(node.argument)
break
case 'BinaryExpression':
case 'LogicalExpression':
VP(node.left)
VP(node.right)
break
case 'FunctionDeclaration':
VP(node.identifier, false)
VP(node.parameters)
VP(node.body)
break
case 'ForGenericStatement':
VP(node.variables)
VP(node.iterators, false)
VP(node.body)
break
case 'IfClause':
case 'ElseifClause':
case 'WhileStatement':
case 'RepeatStatement':
VP(node.condition, false)
/* fall through */
case 'Chunk':
case 'ElseClause':
case 'DoStatement':
VP(node.body)
// VK(node.globals)
// VK(node.comments)
break
case 'ForNumericStatement':
VP(node.variable)
VP(node.start, false)
VP(node.end, false)
VP(node.step, false)
VP(node.body)
break
case 'ReturnStatement':
VP(node.arguments)
break
case 'IfStatement':
VP(node.clauses)
break
case 'MemberExpression':
VP(node.base)
VP(node.identifier)
break
case 'IndexExpression':
VP(node.base)
VP(node.index)
break
case 'LabelStatement':
VP(node.label)
break
case 'CallStatement':
VP(node.expression)
break
case 'GotoStatement':
VP(node.label)
break
case 'TableConstructorExpression':
VP(node.fields)
break
case 'TableKey':
case 'TableKeyString':
VP(node.key)
/* fall through */
case 'TableValue':
VP(node.value)
break
case 'CallExpression':
VP(node.base)
VP(node.arguments)
break
case 'TableCallExpression':
VP(node.base)
VP(node.arguments)
break
case 'StringCallExpression':
VP(node.base)
VP(node.argument)
// break
// case 'Identifier':
// case 'NumericLiteral':
// case 'BooleanLiteral':
// case 'StringLiteral':
// case 'NilLiteral':
// case 'VarargLiteral':
// case 'BreakStatement':
// case 'Comment':
// break
// default:
// throw new Error(`Unhandled ${node.type}`)
}
}
const scopeToParentScope = new Map<number, number>()
const nodeToScope = new Map<luaparse.Node, number>()
const gotoToParentGoto = new Map<number, number>()
const nodeToGoto = new Map<luaparse.Node, number>()
const setExtraInfo = (ast: luaparse.Chunk): void => {
let scopeID = 0
let gotoID = 0
const visitProp = (node: luaparse.Node, prevScope: number, prevGoto: number): void => {
let nextScope = prevScope
let nextGoto = prevGoto
if (isBlock(node)) {
// set scope info
if (
node.body.findIndex(
n => n.type === 'LocalStatement' || (n.type === 'FunctionDeclaration' && n.isLocal)
) !== -1 ||
(node.type === 'FunctionDeclaration' &&
(node.parameters.length > 0 || (node.identifier && node.identifier.type === 'MemberExpression'))) ||
node.type === 'ForNumericStatement' ||
node.type === 'ForGenericStatement'
) {
scopeID += 1
nextScope = scopeID
nodeToScope.set(node, scopeID)
scopeToParentScope.set(scopeID, prevScope)
}
// set goto info
if (node.body.findIndex(s => s.type === 'LabelStatement' || s.type === 'GotoStatement') !== -1) {
nextGoto = gotoID
nodeToGoto.set(node, gotoID)
if (node.type !== 'Chunk' && node.type !== 'FunctionDeclaration') {
gotoToParentGoto.set(gotoID, prevGoto)
}
gotoID += 1
}
}
// set scope info
else if (node.type === 'Identifier' || node.type === 'VarargLiteral') {
nodeToScope.set(node, prevScope)
}
visitNode(node, visitProp, nextScope, prevScope !== nextScope, nextGoto)
}
visitProp(ast, scopeID, gotoID)
}
const parse = (data: string): string => {
const ast = luaparse.parse(data.replace(/^#.*/, ''), {
scope: false,
comments: false,
luaVersion: '5.3'
})
checkGoto(ast)
setExtraInfo(ast)
return generate(ast).toString()
}
export { parse }

@ -1,508 +0,0 @@
import luaparse from 'luaparse'
class MemExpr extends String {
constructor(base, property) {
super();
this.base = base;
this.property = property;
}
get() {
return `Tget(${this.base}, ${this.property})`;
}
set(value) {
return `Tset(${this.base}, ${this.property}, ${value})`;
}
toString() {
return this.get();
}
valueOf() {
return this.get();
}
}
let scopeIndex = 1;
let functionIndex = 0;
let forLoopIndex = 0;
const UNI_OP_MAP = {
'-': 'unm',
'not': 'not',
'#': 'len'
};
const BIN_OP_MAP = {
'..': 'concat',
'+': 'add',
'-': 'sub',
'*': 'mul',
'/': 'div',
'%': 'mod',
'==': 'eq',
'~=': 'neq',
'<': 'lt',
'>': 'gt',
'<=': 'lte',
'>=': 'gte',
'^': 'pow'
};
const GENERATORS = {
AssignmentStatement(node, scope) {
let assignments = node.variables.map((variable, index) => {
const name = scoped(variable, scope);
if (name instanceof MemExpr) {
return name.set(`__star_tmp[${index}]`);
} else {
const [match, args] = [].concat(name.match(/^\$get\((.*)\)$/));
if (!match) {
throw new Error('Unhandled');
}
return `$set(${args}, __star_tmp[${index}])`;
}
}).join(';\n');
const values = parseExpressionList(node.init, scope).join(', ');
return `__star_tmp = [${values}];${assignments}`;
},
BinaryExpression(node, scope) {
let left = scoped(node.left, scope);
let right = scoped(node.right, scope);
let operator = BIN_OP_MAP[node.operator];
if (isCallExpression(node.left)) {
left += '[0]';
}
if (isCallExpression(node.right)) {
right += '[0]';
}
if (!operator) {
console.info(node);
throw new Error(`Unhandled binary operator: ${node.operator}`);
}
return `__star_op_${operator}(${left}, ${right})`;
},
BooleanLiteral(node) {
return node.value ? 'true' : 'false';
},
BreakStatement(node) {
return 'break';
},
CallStatement(node, scope) {
return generate(node.expression, scope);
},
CallExpression(node, scope) {
let functionName = scoped(node.base, scope);
const args = parseExpressionList(node.arguments, scope);
if (isCallExpression(node.base)) {
args.unshift(`${functionName}[0]`);
} else {
if (functionName instanceof MemExpr && node.base.indexer === ':') {
args.unshift(functionName.base);
}
args.unshift(`${functionName}`);
}
return `__star_call(${args})`;
},
Chunk(node, scope) {
let output = node.body.map(statement => generate(statement, scope) + ';');
return output.join('\n');
},
DoStatement(node, outerScope) {
let { scope, scopeDef } = extendScope(outerScope);
let body = this.Chunk(node, scope);
scopeDef = scopeDef.replace(',', ';');
return `${scopeDef}\n${body}\n$=$${outerScope};`;
},
ElseClause(node, scope) {
let body = this.Chunk(node, scope);
return `{\n${body}\n}`;
},
ElseifClause(node, scope) {
return this.IfClause(node, scope);
},
ForNumericStatement(node, outerScope) {
let { scope, scopeDef } = extendScope(outerScope);
let variableName = generate(node.variable, outerScope);
let start = scoped(node.start, outerScope);
let end = scoped(node.end, outerScope);
let step = node.step === null ? 1 : generate(node.step, outerScope);
let operator = step > 0 ? '<=' : '>=';
let body = this.Chunk(node, scope);
let loopIndex = ++forLoopIndex;
let init = `$${outerScope}._forLoop${loopIndex} = ${start}`;
let cond = `$${outerScope}._forLoop${loopIndex} ${operator} ${end}`;
let after = `$${outerScope}._forLoop${loopIndex} += ${step}`;
let varInit = `$${scope}.setLocal('${variableName}',$${outerScope}._forLoop${loopIndex});`;
return `for (${init}; ${cond}; ${after}) {\n${scopeDef}\n${varInit}\n${body}\n}`;
},
ForGenericStatement(node, outerScope) {
const { scope, scopeDef } = extendScope(outerScope);
const { scope: iterationScope, scopeDef: iterationScopeDef } = extendScope(scope);
const iterators = parseExpressionList(node.iterators, outerScope).join(', ');
const body = this.Chunk(node, iterationScope);
const variables = node.variables.map((variable, index) => {
const name = generate(variable, scope);
return `$setLocal($, '${name}', __star_tmp[${index}])`;
}).join(';\n');
const defs = scopeDef.split(', ');
return `${defs[0]};\n[$${scope}._iterator, $${scope}._table, $${scope}._next] = [${iterators}];\nwhile((__star_tmp = __star_call($${scope}._iterator, $${scope}._table, $${scope}._next)),__star_tmp[0] !== undefined) {\n${iterationScopeDef}\$${scope}._next = __star_tmp[0]\n${variables}\n${body}\n}`;
},
FunctionDeclaration(node, outerScope) {
let { scope, scopeDef } = extendScope(outerScope);
let isAnonymous = !node.identifier;
let identifier = isAnonymous ? '' : generate(node.identifier, outerScope);
let isMemberExpr = identifier instanceof MemExpr;
let params = node.parameters.map((param, index) => {
let name = generate(param, scope);
if (name === '...$.getVarargs()') {
return `$.setVarargs(args)`;
} else {
return `$setLocal($, '${name}', __star_shift(args))`;
}
});
let name;
if (isMemberExpr) {
name = identifier.property.replace(/'/g, '');
if (node.identifier.indexer === ':') {
params.unshift("$setLocal($, 'self', __star_shift(args))");
}
} else {
name = identifier;
}
let paramStr = params.join(';\n');
let body = this.Chunk(node, scope);
let prefix = isAnonymous? '' : 'func$';
let funcDef = `(__star_tmp = function ${prefix}${name}(...args){${scopeDef}\n${paramStr};\n${body} return [];}, __star_tmp.toString=()=>'function: 0x${(++functionIndex).toString(16)}', __star_tmp)`;
if (isAnonymous) {
return funcDef;
} else if (isMemberExpr) {
return identifier.set(funcDef);
} else {
const local = node.isLocal ? 'Local' : '';
return `$set${local}($, '${identifier}', ${funcDef})`;
}
},
Identifier(node, scope) {
return node.name;
},
IfClause(node, scope) {
let condition = scoped(node.condition, scope);
if (isCallExpression(node.condition)) {
condition += '[0]';
}
let body = this.Chunk(node, scope);
return `if (__star_op_bool(${condition})) {\n${body}\n}`;
},
IfStatement(node, scope) {
let clauses = node.clauses.map((clause) => generate(clause, scope));
return clauses.join (' else ');
},
IndexExpression(node, scope) {
let base = scoped(node.base, scope);
let index = scoped(node.index, scope);
if (isCallExpression(node.base)) {
base += '[0]';
}
if (isCallExpression(node.index)) {
index += '[0]';
}
return new MemExpr(base, index);
},
LocalStatement(node, scope) {
let assignments = node.variables.map((variable, index) => {
let name = generate(variable, scope);
return `$setLocal($, '${name}', __star_tmp[${index}])`;
}).join(';\n');
const values = parseExpressionList(node.init, scope).join(', ');
return `__star_tmp = [${values}];${assignments}`;
},
LogicalExpression(node, scope) {
let left = scoped(node.left, scope);
let right = scoped(node.right, scope);
let operator = node.operator;
if (isCallExpression(node.left)) {
left += '[0]';
}
if (isCallExpression(node.right)) {
right += '[0]';
}
if (operator === 'and') {
return `(!__star.op.bool(${left})?${left}:${right})`
} else if (operator === 'or') {
return `(__star.op.bool(${left})?${left}:${right})`
} else {
console.info(node);
throw new Error(`Unhandled logical operator: ${node.operator}`);
}
},
MemberExpression(node, scope) {
let base = scoped(node.base, scope);
let identifier = generate(node.identifier, scope);
if (isCallExpression(node.base)) {
base += '[0]';
}
return new MemExpr(base, `'${identifier}'`);
},
NilLiteral(node) {
return 'undefined';
},
NumericLiteral(node) {
return node.value.toString();
},
RepeatStatement(node, outerScope) {
let { scope, scopeDef } = extendScope(outerScope);
let condition = scoped(node.condition, outerScope);
let body = this.Chunk(node, scope);
return `do{\n${scopeDef}\n${body}\n}while(!(${condition}))`;
},
ReturnStatement(node, scope) {
const args = parseExpressionList(node.arguments, scope).join(', ');
return `return [${args}];`;
},
StringCallExpression(node, scope) {
node.arguments = node.argument;
return this.TableCallExpression(node, scope);
},
StringLiteral(node) {
let raw = node.raw;
if (/^\[\[[^]*]$/m.test(raw)) {
return `\`${raw.substr(2, raw.length - 4).replace(/\\/g, '\\\\')}\``;
} else {
raw = raw.replace(/([^\\])\\(\d{1,3})/g, (_, pre, dec) => `${pre}\\u${('000' + parseInt(dec, 10).toString(16)).substr(-4)}`);
return raw;
}
},
TableCallExpression(node, scope) {
let functionName = scoped(node.base, scope);
let args = [generate(node.arguments, scope)];
if (isCallExpression(node.base)) {
return `__star_call(${functionName}[0],${args})`;
} else {
if (functionName instanceof MemExpr && node.base.indexer === ':') {
args.unshift(functionName.base);
}
return `__star_call(${functionName},${args})`;
}
},
TableConstructorExpression(node, scope) {
let fields = node.fields.map((field, index, arr) => {
if (field.type == 'TableValue') {
const isLastItem = index === arr.length - 1;
return this.TableValue(field, scope, isLastItem);
}
return generate(field, scope);
}).join(';\n');
return `new __star_T(t => {${fields}})`;
},
TableKeyString(node, scope) {
let name = generate(node.key, scope);
let value = scoped(node.value, scope);
return `Tset(t, '${name}', ${value})`;
},
TableKey(node, scope) {
let name = scoped(node.key, scope);
let value = scoped(node.value, scope);
return `Tset(t, ${name}, ${value})`;
},
TableValue(node, scope, isLastItem) {
let value = scoped(node.value, scope);
if (isCallExpression(node.value)) {
value = isLastItem ? `...${value}` : `${value}[0]`;
}
return `Tins(t, ${value})`;
},
UnaryExpression(node, scope) {
let operator = UNI_OP_MAP[node.operator];
let argument = scoped(node.argument, scope);
if (isCallExpression(node.argument)) {
argument += '[0]';
}
if (!operator) {
console.info(node);
throw new Error(`Unhandled unary operator: ${node.operator}`);
}
return `__star_op_${operator}(${argument})`;
},
VarargLiteral(node) {
return '...$.getVarargs()';
},
WhileStatement(node, outerScope) {
let { scope, scopeDef } = extendScope(outerScope);
let condition = scoped(node.condition, outerScope);
let body = this.Chunk(node, scope);
return `while(${condition}) {\n${scopeDef}\n${body}\n}`;
},
};
function parseExpressionList(expressionNodeArray, scope) {
return expressionNodeArray.map((node, index, arr) => {
let value = scoped(node, scope);
if (isCallExpression(node)) {
if (index == arr.length - 1) {
return `...${value}`;
}
return `${value}[0]`;
}
return value;
});
}
function extendScope(outerIndex) {
let scope = scopeIndex++;
let scopeDef = `let $${scope} = $${outerIndex}.extend(), $ = $${scope};`;
return { scope, scopeDef };
}
function scoped(node, scope) {
let value = generate(node, scope);
return node.type === 'Identifier' ? `$get($, '${value}')` : value;
}
function isCallExpression(node) {
return !!node.type.match(/CallExpression$/);
}
function generate(ast, scope) {
let generator = GENERATORS[ast.type];
if (!generator) {
console.info(ast);
throw new Error(`No generator found for: ${ast.type}`);
}
return generator.call(GENERATORS, ast, scope);
}
export function parse(data) {
let ast = luaparse.parse(data);
let js = generate(ast, 0);
return `
"use strict"; if (typeof global === \'undefined\' && typeof window !== \'undefined\') { window.global = window; }
let __star = global.starlight.runtime, $0 = __star.globalScope, $ = $0, __star_tmp;
let __star_call = __star.call, __star_T = __star.T, __star_op_bool = __star.op.bool;
let __star_op_unm = __star.op.unm, __star_op_not = __star.op.not, __star_op_len = __star.op.len, __star_op_concat = __star.op.concat, __star_op_add = __star.op.add, __star_op_sub = __star.op.sub, __star_op_mul = __star.op.mul, __star_op_div = __star.op.div, __star_op_mod = __star.op.mod, __star_op_eq = __star.op.eq, __star_op_neq = __star.op.neq, __star_op_lt = __star.op.lt, __star_op_gt = __star.op.gt, __star_op_lte = __star.op.lte, __star_op_gte = __star.op.gte, __star_op_pow = __star.op.pow;
let __star_op_and = __star.op.and, __star_op_or = __star.op.or;
let Tget, Tset, Tins, $get, $set, $setLocal, __star_shift;
(()=>{
let call = Function.prototype.call, bind = call.bind.bind(call), Tproto = __star_T.prototype, $proto = __star.globalScope.constructor.prototype;
Tget = bind(Tproto.get), Tset = bind(Tproto.set), Tins = bind(Tproto.insert);
$get = bind($proto.get), $set = bind($proto.set), $setLocal = bind($proto.setLocal);
__star_shift = bind(Array.prototype.shift);
})();
${js}
`
}

@ -1,10 +0,0 @@
export default class LuaError extends Error {
constructor(message) {
super();
this.message = message;
}
toString() {
return `LuaError: ${this.message}`;
}
}

@ -1,44 +0,0 @@
const hasOwnProperty = Object.prototype.hasOwnProperty;
export default class Scope {
constructor(variables = {}) {
this._variables = variables;
}
get(key) {
return this._variables[key];
}
set(key, value) {
let vars = this._variables;
if (hasOwnProperty.call(this._variables, key) || !this.parent) {
vars[key] = value;
} else {
this.parent.set(key, value);
}
}
setLocal(key, value) {
this._variables[key] = value;
}
setVarargs(value) {
this._varargs = value;
}
getVarargs() {
return this._varargs || this.parent && this.parent.getVarargs();
}
add(key, value) {
this._variables[key] += value;
}
extend(outerScope) {
let innerVars = Object.create(this._variables);
let scope = new Scope(innerVars);
scope.parent = this;
return scope;
}
}

@ -1,214 +0,0 @@
import { default as LuaError } from './LuaError';
import { type } from './lib/globals';
let count = 0;
let stringLib, getn;
export function registerLibs(libs) {
// Can't import directly because they'll create a circular dependencies. :(
stringLib = libs.string;
getn = libs.getn;
};
export default class Table {
constructor(initialiser) {
this.index = ++count;
this.numValues = [undefined];
this.strValues = {};
this.keys = [];
this.values = [];
this.metatable = null;
if (!initialiser) {
// noop
} else if (typeof initialiser === 'function') {
initialiser(this);
} else {
let isArr = initialiser instanceof Array;
for (let i in initialiser) {
if (initialiser.hasOwnProperty(i)) {
let value = initialiser[i];
if (value === null) value = undefined;
let key = isArr? parseInt(i, 10) + 1: i;
let iterate = (typeof value == 'object' && value.constructor === Object) || value instanceof Array;
this.set(key, iterate? new Table(value) : value);
}
}
}
}
get(key) {
if (!(this instanceof Table)) {
if (type(this) == 'string') {
return stringLib.get(key);
} else if (
type(this) === 'userdata'
|| (type(this) === 'function' && key === 'new') // exception for DOMAPI compat with Moonshine
) {
if (key in this) {
return this[key];
}
}
throw new LuaError(`attempt to index a ${type(this)} value`);
}
let value = this.rawget(key);
if (value === void 0) {
let mt, mm;
if (
(mt = this.metatable)
&& (mm = mt.get('__index'))
) {
switch (mm.constructor) {
case Table: return mm.get(key);
case Function:
value = mm.call(undefined, this, key);
return (value instanceof Array)? value[0] : value;
}
}
}
return value;
}
rawget(key) {
switch (typeof key) {
case 'string': return Object.prototype.hasOwnProperty.call(this.strValues, key) ? this.strValues[key] : void 0;
case 'number':
if (key > 0 && key == key >> 0) {
return this.numValues[key];
}
/* fallthrough */
default:
let index = this.keys.indexOf(key);
return (index >= 0) ? this.values[index] : void 0;
}
}
set(key, value) {
if (!(this instanceof Table)) {
if (type(this) == 'userdata') {
this[key] = value;
return;
}
throw new LuaError(`attempt to index a ${type(this)} value`);
}
let mt, mm;
if (
(mt = this.metatable)
&& (mm = mt.get('__newindex'))
) {
let oldValue;
switch (typeof key) {
case 'string':
oldValue = this.strValues[key];
break;
case 'number':
let positiveIntegerKey = key > 0 && key == key >> 0;
if (positiveIntegerKey) {
oldValue = this.numValues[key];
break;
}
default:
let keys = this.keys;
let index = keys.indexOf(key);
oldValue = index == -1? undefined : this.values[index];
}
if (oldValue === undefined) {
switch (mm.constructor) {
case Table: return mm.set(key, value);
case Function: return mm(this, key, value);
}
}
}
this.rawset(key, value);
}
rawset(key, value) {
if (value instanceof Array) {
value = value[0];
}
switch (typeof key) {
case 'string':
this.strValues[key] = value;
break;
case 'number':
let positiveIntegerKey = key > 0 && key == key >> 0;
if (positiveIntegerKey) {
this.numValues[key] = value;
break;
}
default:
let keys = this.keys;
let index = keys.indexOf(key);
if (index < 0) {
index = keys.length;
keys[index] = key;
}
this.values[index] = value;
}
}
insert(...values) {
this.numValues.push(...values);
}
toString() {
let mt, mm;
if (
(mt = this.metatable)
&& (mm = mt.get('__tostring'))
) {
return mm(this)[0];
} else {
return 'table: 0x' + this.index.toString(16);
}
}
toObject() {
const isArr = getn(this) > 0;
const result = isArr? [] : {};
const numValues = this.numValues;
const strValues = this.strValues;
let i;
const l = numValues.length;
for (i = 1; i < l; i++) {
const propValue = numValues[i];
result[i - 1] = (propValue instanceof Table)? propValue.toObject() : propValue;
}
for (i in strValues) {
if (strValues.hasOwnProperty(i)) {
const propValue = strValues[i];
result[i] = (propValue instanceof Table)? propValue.toObject() : propValue;
}
}
return result;
}
};

@ -1,78 +0,0 @@
import { default as Scope } from './Scope';
import { default as globals, type } from './lib/globals';
import { default as operators } from './operators';
import { default as Table, registerLibs } from './Table';
import { default as LuaError } from './LuaError';
function ensureArray(value) {
return (value instanceof Array) ? value : [value];
}
function call(f, ...args) {
if (!(f instanceof Function)) {
if (f instanceof Table) {
let mt, mm;
if (
(mt = f.metatable)
&& (mm = mt.rawget('__call'))
) {
args.unshift(f);
f = mm;
}
}
if (!(f instanceof Function)) {
let typ = type(f);
throw new LuaError(`attempt to call a ${typ} value`);
}
}
return ensureArray(f(...args));
}
let namespace = global.starlight = global.starlight || {};
let _G = globals;
function init () {
let userEnv = namespace.config && namespace.config.env;
if (userEnv) {
for (let key in userEnv) {
globals.set(key, userEnv[key]);
}
}
}
init();
let runtime = namespace.runtime = {
globalScope: new Scope(globals.strValues),
_G,
op: operators,
T: Table,
LuaError,
call
};
// The following should be configurable
import { default as math } from './lib/math';
_G.set('math', math);
import { default as table, getn } from './lib/table';
_G.set('table', table);
import { default as string } from './lib/string';
_G.set('string', string);
import { default as os } from './lib/os';
_G.set('os', os);
import { default as _package } from './lib/package';
_G.set('package', _package);
registerLibs({ string, getn });

@ -1,454 +0,0 @@
import { default as T } from '../Table';
import { default as LuaError } from '../LuaError';
import { default as stringLib, metatable as stringMetatable } from './string';
import { getn } from './table';
import {
stdout,
coerceToNumber,
coerceToString,
coerceToBoolean,
coerceArgToNumber,
coerceArgToString,
coerceArgToTable
} from '../utils';
const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
function getPackageMethods() {
let packageLib = global.starlight.runtime._G.rawget('package');
if (packageLib === void 0) {
throw new LuaError('error during require(), package lib not found.');
}
let methods = [packageLib.rawget('preload'), packageLib.rawget('loaded')];
getPackageMethods = ()=>methods;
return methods;
}
function ipairsIterator(table, index) {
if (index === void 0) {
throw new LuaError('Bad argument #2 to ipairs() iterator');
}
var nextIndex = index + 1,
numValues = table.numValues;
if (!numValues.hasOwnProperty(nextIndex) || numValues[nextIndex] === void 0) return void 0;
return [nextIndex, numValues[nextIndex]];
}
export const _VERSION = 'Lua 5.1';
export function assert(v, m) {
if (!coerceToBoolean(v)) {
m = coerceArgToString(m, 'assert', 2);
throw new LuaError(m || 'Assertion failed!');
}
return [v, m];
}
// TODO dofile([filename])
export function error(message) {
if (
typeof message !== 'string'
&& typeof message !== 'number'
) {
message = '(error object is not a string)';
}
throw new LuaError(message);
}
// TODO getfenv([f])
export function getmetatable(table) {
if (table && table instanceof T) {
let mt = table.metatable;
if (mt) {
let mm;
return (mm = mt.rawget('__metatable')) ? mm : mt;
}
} else if (typeof table == 'string') {
return stringMetatable;
}
}
export function ipairs(t) {
t = coerceArgToTable(t, 'ipairs', 1);
const mt = getmetatable(t);
const mm = mt && mt.get('__ipairs');
return mm ? mm(t).slice(0, 3) : [ipairsIterator, t, 0];
}
// TODO load(func [, chunkname])
// TODO loadfile([filename])
export function loadstring(str, chunkname) {
str = coerceArgToString(str, 'loadstring', 1);
let parser = global.starlight.parser;
if (!parser) {
throw new Error('Starlight parser not found in call to loadstring(). The parser is required to execute Lua strings at runtime.');
}
try {
return parser.parse(str);
} catch (e) {
return [undefined, e.message];
}
}
export function next(table, index) {
table = coerceArgToTable(table, 'next', 1);
// SLOOOOOOOW...
let found = (index === void 0),
key, value,
i, l;
if (found || (typeof index == 'number' && index > 0 && index == index >> 0)) {
let numValues = table.numValues;
if ('keys' in Object) {
// Use Object.keys, if available.
let keys = Object['keys'](numValues);
if (found) {
// First pass
i = 1;
} else if (i = keys.indexOf('' + index) + 1) {
found = true;
}
if (found) {
while ((key = keys[i]) !== void 0 && (value = numValues[key]) === void 0) i++;
if (value !== void 0) return [key >>= 0, value];
}
} else {
// Else use for-in (faster than for loop on tables with large holes)
for (l in numValues) {
i = l >> 0;
if (!found) {
if (i === index) found = true;
} else if (numValues[i] !== void 0) {
return [i, numValues[i]];
}
}
}
}
for (i in table.strValues) {
if (table.strValues.hasOwnProperty(i)) {
if (!found) {
if (i == index) found = true;
} else if (table.strValues[i] !== void 0) {
return [i, table.strValues[i]];
}
}
}
for (i in table.keys) {
if (table.keys.hasOwnProperty(i)) {
let key = table.keys[i];
if (!found) {
if (key === index) found = true;
} else if (table.values[i] !== void 0) {
return [key, table.values[i]];
}
}
}
return [];
}
export function pairs(table) {
table = coerceArgToTable(table, 'pairs', 1);
const mt = getmetatable(table);
const mm = mt && mt.get('__pairs');
return mm ? mm(table).slice(0, 3) : [next, table];
}
export function pcall(func, ...args) {
let result;
try {
if (typeof func == 'function') {
result = func(...args);
} else {
throw new LuaError('Attempt to call non-function');
}
} catch (e) {
return [false, e && e.toString()];
}
result = [].concat(result);
return [true, ...result];
}
export function print(...args) {
let output = args.map(arg => tostring(arg)).join('\t');
stdout.writeln(output);
}
export function rawequal(v1, v2) {
return v1 === v2;
}
export function rawget(table, index) {
table = coerceArgToTable(table, 'rawget', 1);
return table.rawget(index);
}
export function rawset(table, index, value) {
table = coerceArgToTable(table, 'rawset', 1);
if (index === void 0) throw new LuaError('table index is nil');
table.rawset(index, value);
return table;
}
export function _require(modname) {
modname = coerceArgToString(modname, 'require', 1);
modname = modname.replace(/\//g, '.');
let [preload, loaded] = getPackageMethods();
let mod = loaded.rawget(modname);
if (mod) {
return mod;
}
let modinit = preload.rawget(modname);
if (modinit === void 0) {
throw new LuaError(`module '${modname}' not found:\n\tno field package.preload['${modname}']`);
}
let modResult = modinit(modname);
mod = (modResult instanceof Array) ? modResult[0] : modResult;
loaded.rawset(modname, mod !== void 0 ? mod : true);
return mod;
}
export function select(index, ...args) {
if (index === '#') {
return args.length;
} else if (index = parseInt(index, 10)) {
return args.slice(index - 1);
} else {
let typ = type(index);
throw new LuaError(`bad argument #1 to 'select' (number expected, got ${typ})`);
}
}
// TODO setfenv(f, table)
export function setmetatable(table, metatable) {
table = coerceArgToTable(table, 'setmetatable', 1);
if (metatable !== void 0) {
metatable = coerceArgToTable(metatable, 'setmetatable', 2);
}
let mt;
if (
(mt = table.metatable)
&& mt.rawget('__metatable')
) {
throw new LuaError('cannot change a protected metatable');
}
table.metatable = metatable;
return table;
}
export function tonumber(e, base = 10) {
base = coerceArgToNumber(base, 'tonumber', 2);
if (e === '') return;
if (base < 2 || base > 36) {
throw new LuaError(`bad argument #2 to 'tonumber' (base out of range)`);
}
if (base == 10 && (e === Infinity || e === -Infinity || (typeof e == 'number' && global.isNaN(e)))) {
return e;
}
if (base != 10 && e === void 0) {
throw new LuaError("bad argument #1 to 'tonumber' (string expected, got nil)");
}
e = `${e}`.trim();
// If using base 10, use normal coercion.
if (base === 10) {
return coerceToNumber(e);
}
e = coerceToString(e);
// If using base 16, ingore any "0x" prefix
let match;
if (
base === 16
&& (match = e.match(/^(\-)?0[xX](.+)$/))
) {
e = `${match[1] || ''}${match[2]}`;
}
let pattern = new RegExp('^[' + CHARS.substr(0, base) + ']*$', 'gi');
if (!pattern.test(e)) return; // Invalid
return parseInt(e, base);
}
export function tostring(e) {
let mt, mm;
if (
e !== void 0
&& e instanceof T
&& (mt = e.metatable)
&& (mm = mt.rawget('__tostring'))
) {
return mm.call(mm, e);
}
if (e instanceof T) {
return e.toString();
} else if (e instanceof Function) {
return e.hasOwnProperty('toString')? `${e}` : 'function: [host code]';
}
return coerceToString(e);
}
export function type(v) {
let t = typeof v;
switch (t) {
case 'undefined':
return 'nil';
case 'number':
case 'string':
case 'boolean':
case 'function':
return t;
case 'object':
if (v.constructor === T) return 'table';
if (v && v instanceof Function) return 'function';
return 'userdata';
}
}
export function unpack(table, i = 1, j) {
table = coerceArgToTable(table, 'unpack', 1);
i = coerceArgToNumber(i, 'unpack', 2);
if (j === void 0) {
j = getn(table);
} else {
j = coerceArgToNumber(j, 'unpack', 3);
}
return table.numValues.slice(i, j + 1);
}
export function xpcall(func, err) {
let result, success, invalid;
try {
if (typeof func === 'function') {
result = func();
} else {
invalid = true;
}
success = true;
} catch (e) {
result = err(void 0, true)[0];
success = false;
}
if (invalid) throw new LuaError('Attempt to call non-function');
if (!(result && result instanceof Array)) result = [result];
result.unshift(success);
return result;
}
export const _G = new T({
_VERSION,
assert,
error,
getmetatable,
ipairs,
loadstring,
next,
pairs,
pcall,
print,
rawequal,
rawget,
rawset,
require: _require,
select,
setmetatable,
tonumber,
tostring,
type,
unpack,
xpcall,
});
_G.rawset('_G', _G);
export default _G;

@ -1,253 +0,0 @@
import { default as T } from '../Table';
import { coerceArgToNumber } from '../utils';
const RANDOM_MULTIPLIER = 16807;
const RANDOM_MODULUS = 2147483647;
let randomSeed = 1;
function getRandom () {
randomSeed = (RANDOM_MULTIPLIER * randomSeed) % RANDOM_MODULUS;
return randomSeed / RANDOM_MODULUS;
}
export function abs(x) {
x = coerceArgToNumber(x, 'abs', 1);
return Math.abs(x);
}
export function acos(x) {
x = coerceArgToNumber(x, 'acos', 1);
return Math.acos(x);
}
export function asin(x) {
x = coerceArgToNumber(x, 'asin', 1);
return Math.asin(x);
}
export function atan(x) {
x = coerceArgToNumber(x, 'atan', 1);
return Math.atan(x);
}
export function atan2(y, x) {
y = coerceArgToNumber(y, 'atan2', 1);
x = coerceArgToNumber(x, 'atan2', 2);
return Math.atan2(y, x);
}
export function ceil(x) {
x = coerceArgToNumber(x, 'ceil', 1);
return Math.ceil(x);
}
export function cos(x) {
x = coerceArgToNumber(x, 'cos', 1);
return Math.cos(x);
}
export function cosh(x) {
x = coerceArgToNumber(x, 'cosh', 1);
return (exp(x) + exp(-x)) / 2;
}
export function deg(x) {
x = coerceArgToNumber(x, 'deg', 1);
return x * 180 / Math.PI;
}
export function exp(x) {
x = coerceArgToNumber(x, 'exp', 1);
return Math.exp(x);
}
export function floor(x) {
x = coerceArgToNumber(x, 'floor', 1);
return Math.floor(x);
}
export function fmod(x, y) {
x = coerceArgToNumber(x, 'fmod', 1);
y = coerceArgToNumber(y, 'fmod', 2);
return x % y;
}
export function frexp(x) {
x = coerceArgToNumber(x, 'frexp', 1);
if (x === 0) {
return [0, 0];
}
let delta = x > 0? 1 : -1;
x *= delta;
let exponent = Math.floor(Math.log(x) / Math.log(2)) + 1;
let mantissa = x / Math.pow(2, exponent);
return [mantissa * delta, exponent];
}
export const huge = Infinity;
export function ldexp(m, e) {
m = coerceArgToNumber(m, 'ldexp', 1);
e = coerceArgToNumber(e, 'ldexp', 2);
return m * Math.pow(2, e);
}
export function log(x, base) {
x = coerceArgToNumber(x, 'log', 1);
if (base === void 0) {
return Math.log(x);
} else {
y = coerceArgToNumber(y, 'log', 2);
return Math.log(x) / Math.log(base);
}
}
export function log10(x) {
x = coerceArgToNumber(x, 'log10', 1);
// v5.2: shine.warn ('math.log10 is deprecated. Use math.log with 10 as its second argument instead.');
return Math.log(x) / Math.log(10);
}
export function max(...args) {
return Math.max(...args);
}
export function min(...args) {
return Math.min(...args);
}
export function modf(x) {
x = coerceArgToNumber(x, 'modf', 1);
let intValue = Math.floor(x);
let mantissa = x - intValue;
return [intValue, mantissa];
}
export const pi = Math.PI;
export function pow(x, y) {
x = coerceArgToNumber(x, 'pow', 1);
y = coerceArgToNumber(y, 'pow', 2);
return Math.pow(x, y);
}
export function rad(x) {
x = coerceArgToNumber(x, 'rad', 1);
return (Math.PI / 180) * x;
}
export function random(min, max) {
if (min === void 0 && max === void 0) return getRandom();
min = coerceArgToNumber(min, 'random', 1);
if (max === void 0) {
max = min;
min = 1;
} else {
max = coerceArgToNumber(max, 'random', 2);
}
if (min > max) throw new shine.Error("bad argument #2 to 'random' (interval is empty)");
return Math.floor(getRandom() * (max - min + 1) + min);
}
export function randomseed(x) {
x = coerceArgToNumber(x, 'randomseed', 1);
randomSeed = x;
}
export function sin(x) {
x = coerceArgToNumber(x, 'sin', 1);
return Math.sin(x);
}
export function sinh(x) {
x = coerceArgToNumber(x, 'sinh', 1);
return (exp(x) - exp(-x)) / 2;
}
export function sqrt(x) {
x = coerceArgToNumber(x, 'sqrt', 1);
return Math.sqrt(x);
}
export function tan(x) {
x = coerceArgToNumber(x, 'tan', 1);
return Math.tan(x);
}
export function tanh(x) {
x = coerceArgToNumber(x, 'tanh', 1);
return (exp(x) - exp(-x))/(exp(x) + exp(-x));
}
export default new T({
abs,
acos,
asin,
atan,
atan2,
ceil,
cos,
cosh,
deg,
exp,
floor,
fmod,
frexp,
huge,
ldexp,
log,
log10,
max,
min,
modf,
pi,
pow,
rad,
random,
randomseed,
sin,
sinh,
sqrt,
tan,
tanh
});

@ -1,115 +0,0 @@
import { default as T } from '../Table';
const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const DATE_FORMAT_HANDLERS = {
'%a': (d, utc) => DAYS[d['get' + (utc? 'UTC' : '') + 'Day']()].substr(0, 3),
'%A': (d, utc) => DAYS[d['get' + (utc? 'UTC' : '') + 'Day']()],
'%b': (d, utc) => MONTHS[d['get' + (utc? 'UTC' : '') + 'Month']()].substr(0, 3),
'%B': (d, utc) => MONTHS[d['get' + (utc? 'UTC' : '') + 'Month']()],
'%c': (d, utc) => d['to' + (utc? 'UTC' : '') + 'LocaleString'](),
'%d': (d, utc) => ('0' + d['get' + (utc? 'UTC' : '') + 'Date']()).substr(-2),
'%H': (d, utc) => ('0' + d['get' + (utc? 'UTC' : '') + 'Hours']()).substr(-2),
'%I': (d, utc) => ('0' + ((d['get' + (utc? 'UTC' : '') + 'Hours']() + 11) % 12 + 1)).substr(-2),
'%j': (d, utc) => {
let result = d['get' + (utc? 'UTC' : '') + 'Date']();
let m = d['get' + (utc? 'UTC' : '') + 'Month']();
for (let i = 0; i < m; i++) {
result += DAYS_IN_MONTH[i];
}
if (m > 1 && d['get' + (utc? 'UTC' : '') + 'FullYear']() % 4 === 0) {
result +=1;
}
return ('00' + result).substr(-3);
},
'%m': (d, utc) => ('0' + (d['get' + (utc? 'UTC' : '') + 'Month']() + 1)).substr(-2),
'%M': (d, utc) => ('0' + d['get' + (utc? 'UTC' : '') + 'Minutes']()).substr(-2),
'%p': (d, utc) => (d['get' + (utc? 'UTC' : '') + 'Hours']() < 12)? 'AM' : 'PM',
'%S': (d, utc) => ('0' + d['get' + (utc? 'UTC' : '') + 'Seconds']()).substr(-2),
'%U': (d, utc) => getWeekOfYear(d, 0, utc),
'%w': (d, utc) => '' + (d['get' + (utc? 'UTC' : '') + 'Day']()),
'%W': (d, utc) => getWeekOfYear(d, 1, utc),
'%x': (d, utc) => DATE_FORMAT_HANDLERS['%m'](d, utc) + '/' + DATE_FORMAT_HANDLERS['%d'](d, utc) + '/' + DATE_FORMAT_HANDLERS['%y'](d, utc),
'%X': (d, utc) => DATE_FORMAT_HANDLERS['%H'](d, utc) + ':' + DATE_FORMAT_HANDLERS['%M'](d, utc) + ':' + DATE_FORMAT_HANDLERS['%S'](d, utc),
'%y': (d, utc) => DATE_FORMAT_HANDLERS['%Y'](d, utc).substr (-2),
'%Y': (d, utc) => '' + d['get' + (utc? 'UTC' : '') + 'FullYear'](),
'%Z': (d, utc) => { let m; return (utc && 'UTC') || ((m = d.toString().match(/[A-Z][A-Z][A-Z]/)) && m[0]); },
'%%': () => '%'
}
function isDST(date) {
let year = date.getFullYear();
let jan = new Date(year, 0);
// ASSUMPTION: If the time offset of the date is the same as it would be in January of the same year, DST is not in effect.
return (date.getTimezoneOffset() !== jan.getTimezoneOffset());
}
function getWeekOfYear (d, firstDay, utc) {
let dayOfYear = parseInt(DATE_FORMAT_HANDLERS['%j'](d), 10);
let jan1 = new Date(d.getFullYear (), 0, 1, 12);
let offset = (8 - jan1['get' + (utc? 'UTC' : '') + 'Day']() + firstDay) % 7;
return ('0' + (Math.floor((dayOfYear - offset) / 7) + 1)).substr(-2);
}
export function date(format = '%c', time) {
let utc,
date = new Date();
if (time) {
date.setTime(time * 1000);
}
if (format.substr(0, 1) === '!') {
format = format.substr(1);
utc = true;
}
if (format === '*t') {
return new T ({
year: parseInt(DATE_FORMAT_HANDLERS['%Y'](date, utc), 10),
month: parseInt(DATE_FORMAT_HANDLERS['%m'](date, utc), 10),
day: parseInt(DATE_FORMAT_HANDLERS['%d'](date, utc), 10),
hour: parseInt(DATE_FORMAT_HANDLERS['%H'](date, utc), 10),
min: parseInt(DATE_FORMAT_HANDLERS['%M'](date, utc), 10),
sec: parseInt(DATE_FORMAT_HANDLERS['%S'](date, utc), 10),
wday: parseInt(DATE_FORMAT_HANDLERS['%w'](date, utc), 10) + 1,
yday: parseInt(DATE_FORMAT_HANDLERS['%j'](date, utc), 10),
isdst: isDST(date)
});
}
for (let i in DATE_FORMAT_HANDLERS) {
if (DATE_FORMAT_HANDLERS.hasOwnProperty(i) && format.indexOf(i) >= 0) {
format = format.replace(i, DATE_FORMAT_HANDLERS[i](date, utc));
}
}
return format;
}
export function exit(code = 0) {
var process = global.process;
if (process && process.exit) {
process.exit(code);
} else {
throw new Error(`Exit with code: ${code}`);
}
}
export default new T({
date,
exit
});

@ -1,7 +0,0 @@
import { default as T } from '../Table';
export default new T({
preload: new T(),
loaded: new T()
});

@ -1,319 +0,0 @@
import { default as T } from '../Table';
import { default as LuaError } from '../LuaError';
import { tostring } from './string';
import printf from 'printf';
import {
coerceToNumber,
coerceToString,
coerceToBoolean,
coerceArgToNumber,
coerceArgToString,
coerceArgToFunction
} from '../utils';
const ROSETTA_STONE = {
'([^a-zA-Z0-9%(])-': '$1*?',
'([^%])-([^a-zA-Z0-9?])': '$1*?$2',
'(.)-$': '$1*?',
'%a': '[a-zA-Z]',
'%A': '[^a-zA-Z]',
'%c': '[\x00-\x1f]',
'%C': '[^\x00-\x1f]',
'%d': '\\d',
'%D': '[^\d]',
'%l': '[a-z]',
'%L': '[^a-z]',
'%p': '[\.\,\"\'\?\!\;\:\#\$\%\&\(\)\*\+\-\/\<\>\=\@\\[\\]\\\\^\_\{\}\|\~]',
'%P': '[^\.\,\"\'\?\!\;\:\#\$\%\&\(\)\*\+\-\/\<\>\=\@\\[\\]\\\\^\_\{\}\|\~]',
'%s': '[ \\t\\n\\f\\v\\r]',
'%S': '[^ \t\n\f\v\r]',
'%u': '[A-Z]',
'%U': '[^A-Z]',
'%w': '[a-zA-Z0-9]',
'%W': '[^a-zA-Z0-9]',
'%x': '[a-fA-F0-9]',
'%X': '[^a-fA-F0-9]',
'%([^a-zA-Z])': '\\$1'
};
function translatePattern (pattern) {
// TODO Add support for balanced character matching (not sure this is easily achieveable).
pattern = '' + pattern;
// Replace single backslash with double backslashes
pattern = pattern.replace(new RegExp('\\\\', 'g'), '\\\\');
for (let i in ROSETTA_STONE) {
if (ROSETTA_STONE.hasOwnProperty(i)) {
pattern = pattern.replace(new RegExp(i, 'g'), ROSETTA_STONE[i]);
}
}
let l = pattern.length;
let n = 0;
for (let i = 0; i < l; i++) {
const character = pattern.substr(i, 1);
if (i && pattern.substr(i - 1, 1) == '\\') {
continue;
}
let addSlash = false;
if (character == '[') {
if (n) addSlash = true;
n++;
} else if (character == ']' && pattern.substr(i - 1, 1) !== '\\') {
n--;
if (n) addSlash = true;
}
if (addSlash) {
pattern = pattern.substr(0, i) + pattern.substr(i++ + 1);
l++;
}
}
return pattern;
}
export function byte(s, i = 1, j) {
s = coerceArgToString(s, 'byte', 1);
i = coerceArgToNumber(i, 'byte', 2);
if (j === void 0) {
j = i;
} else {
j = coerceArgToNumber(j, 'byte', 3);
}
return s.substring(i - 1, j).split('').map(c => c.charCodeAt(0));
}
export function char(...bytes) {
return bytes.map((b, i) => {
b = coerceArgToNumber(b, 'char', i);
return String.fromCharCode(b);
}).join('');
}
export function dump(func) {
func = coerceArgToFunction(func, 'dump', 1);
throw new LuaError('string.dump() is not supported');
}
export function find(s, pattern, init = 1, plain = false) {
s = coerceArgToString(s, 'find', 1);
pattern = coerceArgToString(pattern, 'find', 2);
init = coerceArgToNumber(init, 'find', 3);
plain = coerceToBoolean(plain);
// Regex
if (!plain) {
pattern = translatePattern(pattern);
let reg = new RegExp(pattern);
let index = s.substr(init - 1).search(reg);
if (index < 0) return;
let match = s.substr(init - 1).match(reg);
let result = [index + init, index + init + match[0].length - 1];
match.shift();
return result.concat(match);
}
// Plain
let index = s.indexOf(pattern, init - 1);
return (index === -1)? void 0 : [index + 1, index + pattern.length];
}
// TODO string.format (formatstring, ···)
export function format(formatstring, ...args) {
return printf(formatstring, ...args);
}
export function gmatch(s, pattern) {
s = coerceArgToString(s, 'gmatch', 1);
pattern = coerceArgToString(pattern, 'gmatch', 2);
pattern = translatePattern(pattern);
let reg = new RegExp(pattern, 'g'),
matches = s.match(reg);
return () => {
let match = matches.shift(),
groups = new RegExp(pattern).exec(match);
if (match === void 0) {
return;
}
groups.shift();
return groups.length? groups : match;
};
}
export function gsub(s, pattern, repl, n = Infinity) {
s = coerceArgToString(s, 'gsub', 1);
pattern = coerceArgToString(pattern, 'gsub', 2);
n = coerceArgToNumber(n, 'gsub', 3);
pattern = translatePattern('' + pattern);
let replIsFunction = (typeof repl == 'function');
let count = 0,
result = '',
str,
prefix,
match,
lastMatch;
while (
count < n
&& s
&& (match = s.match(pattern))
) {
if (replIsFunction) {
str = repl(match[0]);
if (str instanceof Array) str = str[0];
if (str === void 0) str = match[0];
} else if (repl instanceof T) {
str = repl.get(match[0]);
} else if (typeof repl == 'object') {
str = repl[match];
} else {
str = `${repl}`.replace(/%([0-9])/g, (m, i) => match[i]);
}
if (match[0].length === 0) {
if (lastMatch === void 0) {
prefix = '';
} else {
prefix = s.substr(0, 1);
}
} else {
prefix = s.substr(0, match.index);
}
lastMatch = match[0];
result += `${prefix}${str}`;
s = s.substr(`${prefix}${lastMatch}`.length);
count++;
}
return [`${result}${s}`, count];
}
export function len(s) {
s = coerceArgToString(s, 'len', 1);
return s.length;
}
export function lower (s) {
s = coerceArgToString(s, 'lower', 1);
return s.toLowerCase();
}
export function match(s, pattern, init = 0) {
s = coerceArgToString(s, 'match', 1);
pattern = coerceArgToString(pattern, 'match', 2);
init = coerceArgToNumber(init, 'match', 3);
s = s.substr(init);
let matches = s.match(new RegExp(translatePattern (pattern)));
if (!matches) {
return;
} else if (!matches[1]) {
return matches[0];
}
matches.shift();
return matches;
}
export function rep(s, n) {
s = coerceArgToString(s, 'rep', 1);
n = coerceArgToNumber(n, 'rep', 2);
return Array(n + 1).join(s);
}
export function reverse(s) {
s = coerceArgToString(s, 'reverse', 1);
return Array.prototype.map.call(s, l => l).reverse().join('');
}
export function sub(s, i = 1, j) {
s = coerceArgToString(s, 'sub', 1);
i = coerceArgToNumber(i, 'sub', 2);
if (j === void 0) {
s.length;
} else {
j = coerceArgToNumber(j, 'sub', 3);
}
if (i > 0) {
i = i - 1;
} else if (i < 0) {
i = s.length + i;
}
if (j < 0) {
j = s.length + j + 1;
}
return s.substring(i, j);
}
export function upper(s) {
s = coerceArgToString(s, 'upper', 1);
return s.toUpperCase();
}
const string = new T({
byte,
char,
dump,
find,
format,
gmatch,
gsub,
len,
lower,
match,
rep,
reverse,
sub,
upper
});
export default string;
export const metatable = new T({ __index: string });

@ -1,136 +0,0 @@
import { default as T } from '../Table';
import { default as LuaError } from '../LuaError';
import {
coerceToNumber,
coerceToBoolean,
coerceArgToNumber,
coerceArgToString,
coerceArgToTable,
coerceArgToFunction
} from '../utils';
export function concat(table, sep = '', i = 1, j) {
table = coerceArgToTable(table, 'concat', 1);
sep = coerceArgToString(sep, 'concat', 2);
i = coerceArgToNumber(i, 'concat', 3);
if (j === void 0) {
j = maxn(table);
} else {
j = coerceArgToNumber(j, 'concat', 4);
}
return [].concat(table.numValues).splice(i, j - i + 1).join(sep);
}
export function getn(table) {
table = coerceArgToTable(table, 'getn', 1);
let vals = table.numValues,
keys = [],
j = 0;
for (let i in vals) {
if (vals.hasOwnProperty(i)) {
keys[i] = true;
}
}
while (keys[j + 1]) j++;
// Following translated from ltable.c (http://www.lua.org/source/5.1/ltable.c.html)
if (j > 0 && vals[j] === void 0) {
/* there is a boundary in the array part: (binary) search for it */
let i = 0;
while (j - i > 1) {
let m = Math.floor((i + j) / 2);
if (vals[m] === void 0) {
j = m;
} else {
i = m;
}
}
return i;
}
return j;
}
export function insert(table, index, obj) {
table = coerceArgToTable(table, 'insert', 1);
if (obj === void 0) {
obj = index;
index = table.numValues.length;
} else {
index = coerceArgToNumber(index, 'insert', 2);
}
table.numValues.splice(index, 0, void 0);
table.set(index, obj);
}
export function maxn(table) {
table = coerceArgToTable(table, 'maxn', 1);
return table.numValues.length - 1;
}
export function remove(table, index) {
table = coerceArgToTable(table, 'remove', 1);
index = coerceArgToNumber(index, 'remove', 2);
let max = getn(table);
let vals = table.numValues;
if (index > max) {
return;
}
if (index === void 0) {
index = max;
}
let result = vals.splice(index, 1);
while (index < max && vals[index] === void 0) {
delete vals[index++];
}
return result;
}
export function sort(table, comp) {
table = coerceArgToTable(table, 'sort', 1);
let sortFunc;
let arr = table.numValues;
if (comp) {
comp = coerceArgToFunction(comp, 'sort', 2);
sortFunc = (a, b) => coerceToBoolean(comp(a, b)[0])? -1 : 1;
} else {
sortFunc = (a, b) => a < b? -1 : 1;
}
arr.shift();
arr.sort(sortFunc).unshift(void 0);
}
export default new T({
concat,
getn,
insert,
maxn,
remove,
sort
});

@ -1,157 +0,0 @@
import { default as T } from './Table';
import { coerceToNumber, coerceToBoolean, coerceToString } from './utils';
import { getn } from './lib/table';
import { default as LuaError } from './LuaError';
function binaryArithmetic(left, right, metaMethodName, callback) {
let mt, f;
if ((left && left instanceof T && (mt = left.metatable) && (f = mt.rawget(metaMethodName)))
|| (right && right instanceof T && (mt = right.metatable) && (f = mt.rawget(metaMethodName)))) {
return f(left, right)[0];
}
if (typeof left !== 'number') {
left = coerceToNumber(left, 'attempt to perform arithmetic on a %type value');
}
if (typeof right !== 'number') {
right = coerceToNumber(right, 'attempt to perform arithmetic on a %type value');
}
return callback(left, right);
}
function binaryStringArithmetic(left, right, metaMethodName, callback) {
if (typeof left == 'string' && typeof right == 'string') {
return callback(left, right);
}
return binaryArithmetic(left, right, metaMethodName, callback);
}
function concat(left, right) {
let mt, f;
if (
(left && left instanceof T && (mt = left.metatable) && (f = mt.rawget('__concat')))
|| (right && right instanceof T && (mt = right.metatable) && (f = mt.rawget('__concat')))
) {
return f(left, right)[0];
} else {
right = coerceToString(right, 'attempt to concatenate a %type value');
left = coerceToString(left, 'attempt to concatenate a %type value');
return `${left}${right}`;
}
}
function equal(left, right) {
var mtl, mtr, f, result;
if (right !== left
&& left && left instanceof T
&& right && right instanceof T
&& (mtl = left.metatable)
&& (mtr = right.metatable)
&& mtl === mtr
&& (f = mtl.rawget('__eq'))
) {
return !!f(left, right)[0];
}
return (left === right);
}
function mod(left, right) {
if (
right === 0
|| right === -Infinity
|| right === Infinity
|| global.isNaN(left)
|| global.isNaN(right)
) {
return NaN;
}
let absRight = Math.abs(right);
let result = Math.abs(left) % absRight;
if (left * right < 0) result = absRight - result;
if (right < 0) result *= -1;
return result;
}
function len(value) {
let length, i;
if (value === undefined) throw new LuaError('attempt to get length of a nil value');
if (value instanceof T) return getn(value);
if (typeof value == 'object') {
let length = 0;
for (let key in value) {
if (value.hasOwnProperty(key)) {
length++;
}
}
return length;
}
return value.length;
}
function unaryMinus(value) {
var mt, f, result;
if (value && value instanceof T && (mt = value.metatable) && (f = mt.rawget('__unm'))) {
return f(value)[0];
}
if (typeof value !== 'number') {
value = coerceToNumber(value, 'attempt to perform arithmetic on a %type value');
}
return -value;
}
const op = {
concat,
len,
eq: equal,
unm: unaryMinus,
bool: coerceToBoolean,
neq: (...args) => !equal(...args),
not: (...args) => !coerceToBoolean(...args),
add: (left, right) => binaryArithmetic(left, right, '__add', (l, r) => l + r),
sub: (left, right) => binaryArithmetic(left, right, '__sub', (l, r) => l - r),
mul: (left, right) => binaryArithmetic(left, right, '__mul', (l, r) => l * r),
div: (left, right) => {
if (right === undefined) throw new LuaError('attempt to perform arithmetic on a nil value');
return binaryArithmetic(left, right, '__div', (l, r) => l / r);
},
mod: (left, right) => binaryArithmetic(left, right, '__mod', mod),
pow: (left, right) => binaryArithmetic(left, right, '__pow', Math.pow),
lt: (left, right) => binaryStringArithmetic(left, right, '__lt', (l, r) => l < r),
lte: (left, right) => binaryStringArithmetic(left, right, '__le', (l, r) => l <= r),
gt(left, right) {
return !op.lte(left, right);
},
gte(left, right) {
return !op.lt(left, right);
}
};
export default op;

@ -1,173 +0,0 @@
import { type } from './lib/globals';
import { default as LuaError } from './LuaError';
import { default as T } from './Table';
/**
* Pattern to identify a float string value that can validly be converted to a number in Lua.
* @type RegExp
* @constant
*/
const FLOATING_POINT_PATTERN = /^[-+]?[0-9]*\.?([0-9]+([eE][-+]?[0-9]+)?)?$/;
/**
* Pattern to identify a hex string value that can validly be converted to a number in Lua.
* @type RegExp
* @constant
*/
const HEXIDECIMAL_CONSTANT_PATTERN = /^(\-)?0x([0-9a-fA-F]*)\.?([0-9a-fA-F]*)$/;
function defaultWriteln (...args) {
console.log(...args);
}
/********************
* Stdout
********************/
export const stdout = {
writeln: (() => {
let namespace, config, stdout;
return (namespace = global.starlight)
&& (config = namespace.config)
&& (stdout = config.stdout)
&& stdout.writeln
|| defaultWriteln;
})()
};
/********************
* Coercion
********************/
/**
* Thows an error with the type of a variable included in the message
* @param {Object} val The value whise type is to be inspected.
* @param {String} errorMessage The error message to throw.
* @throws {LuaError}
*/
function throwCoerceError (val, errorMessage) {
if (!errorMessage) return;
errorMessage = ('' + errorMessage).replace(/\%type/gi, type(val));
throw new LuaError(errorMessage);
}
/**
* Coerces a value from its current type to a boolean in the same manner as Lua.
* @param {Object} val The value to be converted.
* @returns {Boolean} The converted value.
*/
export function coerceToBoolean(val) {
return !(val === false || val === void 0);
}
/**
* Coerces a value from its current type to a number in the same manner as Lua.
* @param {Object} val The value to be converted.
* @param {String} [errorMessage] The error message to throw if the conversion fails.
* @returns {Number} The converted value.
*/
export function coerceToNumber(val, errorMessage) {
let n, match, mantissa;
switch (true) {
case typeof val == 'number': return val;
case val === void 0: return;
case val === 'inf': return Infinity;
case val === '-inf': return -Infinity;
case val === 'nan': return NaN;
default:
if (('' + val).match(FLOATING_POINT_PATTERN)) {
n = parseFloat(val);
} else if (match = ('' + val).match(HEXIDECIMAL_CONSTANT_PATTERN)) {
mantissa = match[3];
if ((n = match[2]) || mantissa) {
n = parseInt(n, 16) || 0;
if (mantissa) n += parseInt(mantissa, 16) / Math.pow(16, mantissa.length);
if (match[1]) n *= -1;
}
}
if (n === void 0) {
throwCoerceError(val, errorMessage);
}
return n;
}
}
/**
* Coerces a value from its current type to a string in the same manner as Lua.
* @param {Object} val The value to be converted.
* @param {String} [errorMessage] The error message to throw if the conversion fails.
* @returns {String} The converted value.
*/
export function coerceToString(val, errorMessage) {
switch(true) {
case typeof val == 'string':
return val;
case val === void 0:
case val === null:
return 'nil';
case val === Infinity:
return 'inf';
case val === -Infinity:
return '-inf';
case typeof val == 'number':
case typeof val == 'boolean':
return global.isNaN(val)? 'nan' : `${val}`;
default:
return throwCoerceError(val, errorMessage) || 'userdata';
}
}
function coerceArg(value, coerceFunc, typ, funcName, index) {
return coerceFunc(value, `bad argument #${index} to '${funcName}' (${typ} expected, got %type)`);
}
export function coerceArgToNumber(value, funcName, index) {
return coerceArg(value, coerceToNumber, 'number', funcName, index);
}
export function coerceArgToString(value, funcName, index) {
return coerceArg(value, coerceToString, 'string', funcName, index);
}
export function coerceArgToTable(value, funcName, index) {
if (value instanceof T) {
return value;
} else {
let typ = type(value);
throw new LuaError(`bad argument #${index} to '${funcName}' (table expected, got ${typ})`);
}
}
export function coerceArgToFunction(value, funcName, index) {
if (value instanceof Function) {
return value;
} else {
let typ = type(value);
throw new LuaError(`bad argument #${index} to '${funcName}' (function expected, got ${typ})`);
}
}

@ -0,0 +1,191 @@
import { LuaError } from './LuaError'
import { Table } from './Table'
type LuaType = undefined | boolean | number | string | Function | Table // thread | userdata
interface Config {
LUA_PATH?: string
fileExists?: (path: string) => boolean
loadFile?: (path: string) => string
stdin?: string
stdout?: (data: string) => void
osExit?: (code: number) => void
}
/** Pattern to identify a float string value that can validly be converted to a number in Lua */
const FLOATING_POINT_PATTERN = /^[-+]?[0-9]*\.?([0-9]+([eE][-+]?[0-9]+)?)?$/
/** Pattern to identify a hex string value that can validly be converted to a number in Lua */
const HEXIDECIMAL_CONSTANT_PATTERN = /^(-)?0x([0-9a-fA-F]*)\.?([0-9a-fA-F]*)$/
function type(v: LuaType): 'string' | 'number' | 'boolean' | 'function' | 'nil' | 'table' {
const t = typeof v
switch (t) {
case 'undefined':
return 'nil'
case 'number':
case 'string':
case 'boolean':
case 'function':
return t
case 'object':
if (v instanceof Table) return 'table'
if (v instanceof Function) return 'function'
}
}
/* translate a relative string position: negative means back from end */
function posrelat(pos: number, len: number): number {
if (pos >= 0) return pos
if (-pos > len) return 0
return len + pos + 1
}
/**
* Thows an error with the type of a variable included in the message
* @param {Object} val The value whise type is to be inspected.
* @param {String} errorMessage The error message to throw.
* @throws {LuaError}
*/
function throwCoerceError(val: LuaType, errorMessage?: string): undefined {
if (!errorMessage) return undefined
throw new LuaError(`${errorMessage}`.replace(/%type/gi, type(val)))
}
/**
* Coerces a value from its current type to a boolean in the same manner as Lua.
* @param {Object} val The value to be converted.
* @returns {Boolean} The converted value.
*/
function coerceToBoolean(val: LuaType): boolean {
return !(val === false || val === undefined)
}
/**
* Coerces a value from its current type to a number in the same manner as Lua.
* @param {Object} val The value to be converted.
* @param {String} [errorMessage] The error message to throw if the conversion fails.
* @returns {Number} The converted value.
*/
function coerceToNumber(val: LuaType, errorMessage?: string): number {
if (typeof val === 'number') return val
switch (val) {
case undefined:
return undefined
case 'inf':
return Infinity
case '-inf':
return -Infinity
case 'nan':
return NaN
}
const V = `${val}`
if (V.match(FLOATING_POINT_PATTERN)) {
return parseFloat(V)
}
const match = V.match(HEXIDECIMAL_CONSTANT_PATTERN)
if (match) {
const [, sign, exponent, mantissa] = match
let n = parseInt(exponent, 16) || 0
if (mantissa) n += parseInt(mantissa, 16) / Math.pow(16, mantissa.length)
if (sign) n *= -1
return n
}
if (errorMessage === undefined) return undefined
throwCoerceError(val, errorMessage)
}
/**
* Coerces a value from its current type to a string in the same manner as Lua.
* @param {Object} val The value to be converted.
* @param {String} [errorMessage] The error message to throw if the conversion fails.
* @returns {String} The converted value.
*/
function coerceToString(val: LuaType, errorMessage?: string): string {
if (typeof val === 'string') return val
switch (val) {
case undefined:
case null:
return 'nil'
case Infinity:
return 'inf'
case -Infinity:
return '-inf'
}
if (typeof val === 'number') {
return Number.isNaN(val) ? 'nan' : `${val}`
}
if (typeof val === 'boolean') {
return `${val}`
}
if (errorMessage === undefined) return 'nil'
throwCoerceError(val, errorMessage)
}
function coerceArg<T>(
value: LuaType,
coerceFunc: (val: LuaType, errorMessage?: string) => T,
typ: 'number' | 'string',
funcName: string,
index: number
): T {
return coerceFunc(value, `bad argument #${index} to '${funcName}' (${typ} expected, got %type)`)
}
function coerceArgToNumber(value: LuaType, funcName: string, index: number): number {
return coerceArg<number>(value, coerceToNumber, 'number', funcName, index)
}
function coerceArgToString(value: LuaType, funcName: string, index: number): string {
return coerceArg<string>(value, coerceToString, 'string', funcName, index)
}
function coerceArgToTable(value: LuaType, funcName: string, index: number): Table {
if (value instanceof Table) {
return value
} else {
const typ = type(value)
throw new LuaError(`bad argument #${index} to '${funcName}' (table expected, got ${typ})`)
}
}
function coerceArgToFunction(value: LuaType, funcName: string, index: number): Function {
if (value instanceof Function) {
return value
} else {
const typ = type(value)
throw new LuaError(`bad argument #${index} to '${funcName}' (function expected, got ${typ})`)
}
}
const ensureArray = <T>(value: T | T[]): T[] => (value instanceof Array ? value : [value])
const hasOwnProperty = (obj: Record<string, unknown> | unknown[], key: string | number): boolean =>
Object.prototype.hasOwnProperty.call(obj, key)
export {
LuaType,
Config,
type,
posrelat,
coerceToBoolean,
coerceToNumber,
coerceToString,
coerceArgToNumber,
coerceArgToString,
coerceArgToTable,
coerceArgToFunction,
ensureArray,
hasOwnProperty
}

@ -0,0 +1,293 @@
#!../lua
-- $Id: all.lua,v 1.95 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice at the end of this file
local version = "Lua 5.3"
if _VERSION ~= version then
io.stderr:write("\nThis test suite is for ", version, ", not for ", _VERSION,
"\nExiting tests\n")
return
end
_G._ARG = arg -- save arg for other tests
-- next variables control the execution of some tests
-- true means no test (so an undefined variable does not skip a test)
-- defaults are for Linux; test everything.
-- Make true to avoid long or memory consuming tests
_soft = rawget(_G, "_soft") or false
-- Make true to avoid non-portable tests
_port = rawget(_G, "_port") or false
-- Make true to avoid messages about tests not performed
_nomsg = rawget(_G, "_nomsg") or false
local usertests = rawget(_G, "_U")
if usertests then
-- tests for sissies ;) Avoid problems
_soft = true
_port = true
_nomsg = true
end
-- tests should require debug when needed
debug = nil
if usertests then
T = nil -- no "internal" tests for user tests
else
T = rawget(_G, "T") -- avoid problems with 'strict' module
end
math.randomseed(0)
--[=[
example of a long [comment],
[[spanning several [lines]]]
]=]
print("current path:\n****" .. package.path .. "****\n")
local initclock = os.clock()
local lastclock = initclock
local walltime = os.time()
local collectgarbage = collectgarbage
do -- (
-- track messages for tests not performed
local msgs = {}
function Message (m)
if not _nomsg then
print(m)
msgs[#msgs+1] = string.sub(m, 3, -3)
end
end
assert(os.setlocale"C")
local T,print,format,write,assert,type,unpack,floor =
T,print,string.format,io.write,assert,type,table.unpack,math.floor
-- use K for 1000 and M for 1000000 (not 2^10 -- 2^20)
local function F (m)
local function round (m)
m = m + 0.04999
return format("%.1f", m) -- keep one decimal digit
end
if m < 1000 then return m
else
m = m / 1000
if m < 1000 then return round(m).."K"
else
return round(m/1000).."M"
end
end
end
local showmem
if not T then
local max = 0
showmem = function ()
local m = collectgarbage("count") * 1024
max = (m > max) and m or max
print(format(" ---- total memory: %s, max memory: %s ----\n",
F(m), F(max)))
end
else
showmem = function ()
T.checkmemory()
local total, numblocks, maxmem = T.totalmem()
local count = collectgarbage("count")
print(format(
"\n ---- total memory: %s (%.0fK), max use: %s, blocks: %d\n",
F(total), count, F(maxmem), numblocks))
print(format("\t(strings: %d, tables: %d, functions: %d, "..
"\n\tudata: %d, threads: %d)",
T.totalmem"string", T.totalmem"table", T.totalmem"function",
T.totalmem"userdata", T.totalmem"thread"))
end
end
--
-- redefine dofile to run files through dump/undump
--
local function report (n) print("\n***** FILE '"..n.."'*****") end
local olddofile = dofile
local dofile = function (n, strip)
showmem()
local c = os.clock()
print(string.format("time: %g (+%g)", c - initclock, c - lastclock))
lastclock = c
report(n)
local f = assert(loadfile(n))
local b = string.dump(f, strip)
f = assert(load(b))
return f()
end
-- TODO: consider implementing
-- dofile('main.lua')
do
local next, setmetatable, stderr = next, setmetatable, io.stderr
-- track collections
local mt = {}
-- each time a table is collected, remark it for finalization
-- on next cycle
mt.__gc = function (o)
stderr:write'.' -- mark progress
local n = setmetatable(o, mt) -- remark it
end
local n = setmetatable({}, mt) -- create object
end
report"gc.lua"
local f = assert(loadfile('gc.lua'))
f()
dofile('db.lua')
assert(dofile('calls.lua') == deep and deep)
olddofile('strings.lua')
olddofile('literals.lua')
dofile('tpack.lua')
assert(dofile('attrib.lua') == 27)
assert(dofile('locals.lua') == 5)
dofile('constructs.lua')
dofile('code.lua', true)
if not _G._soft then
report('big.lua')
local f = coroutine.wrap(assert(loadfile('big.lua')))
assert(f() == 'b')
assert(f() == 'a')
end
dofile('nextvar.lua')
dofile('pm.lua')
dofile('utf8.lua')
dofile('api.lua')
assert(dofile('events.lua') == 12)
dofile('vararg.lua')
dofile('closure.lua')
dofile('coroutine.lua')
dofile('goto.lua', true)
dofile('errors.lua')
dofile('math.lua')
dofile('sort.lua', true)
dofile('bitwise.lua')
assert(dofile('verybig.lua', true) == 10); collectgarbage()
-- TODO: consider implementing
-- dofile('files.lua')
if #msgs > 0 then
print("\ntests not performed:")
for i=1,#msgs do
print(msgs[i])
end
print()
end
-- no test module should define 'debug'
assert(debug == nil)
local debug = require "debug"
print(string.format("%d-bit integers, %d-bit floats",
string.packsize("j") * 8, string.packsize("n") * 8))
debug.sethook(function (a) assert(type(a) == 'string') end, "cr")
-- to survive outside block
_G.showmem = showmem
end --)
local _G, showmem, print, format, clock, time, difftime, assert, open =
_G, showmem, print, string.format, os.clock, os.time, os.difftime,
assert, io.open
-- file with time of last performed test
local fname = T and "time-debug.txt" or "time.txt"
local lasttime
if not usertests then
-- open file with time of last performed test
local f = io.open(fname)
if f then
lasttime = assert(tonumber(f:read'a'))
f:close();
else -- no such file; assume it is recording time for first time
lasttime = nil
end
end
-- erase (almost) all globals
print('cleaning all!!!!')
for n in pairs(_G) do
if not ({___Glob = 1, tostring = 1})[n] then
_G[n] = nil
end
end
collectgarbage()
collectgarbage()
collectgarbage()
collectgarbage()
collectgarbage()
collectgarbage();showmem()
local clocktime = clock() - initclock
walltime = difftime(time(), walltime)
print(format("\n\ntotal time: %.2fs (wall time: %gs)\n", clocktime, walltime))
if not usertests then
lasttime = lasttime or clocktime -- if no last time, ignore difference
-- check whether current test time differs more than 5% from last time
local diff = (clocktime - lasttime) / lasttime
local tolerance = 0.05 -- 5%
if (diff >= tolerance or diff <= -tolerance) then
print(format("WARNING: time difference from previous test: %+.1f%%",
diff * 100))
end
assert(open(fname, "w")):write(clocktime):close()
end
print("final OK !!!")
--[[
*****************************************************************************
* Copyright (C) 1994-2016 Lua.org, PUC-Rio.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*****************************************************************************
]]

File diff suppressed because it is too large Load Diff

@ -0,0 +1,470 @@
-- $Id: attrib.lua,v 1.65 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print "testing require"
assert(require"string" == string)
assert(require"math" == math)
assert(require"table" == table)
assert(require"io" == io)
assert(require"os" == os)
assert(require"coroutine" == coroutine)
assert(type(package.path) == "string")
assert(type(package.cpath) == "string")
assert(type(package.loaded) == "table")
assert(type(package.preload) == "table")
assert(type(package.config) == "string")
print("package config: "..string.gsub(package.config, "\n", "|"))
do
-- create a path with 'max' templates,
-- each with 1-10 repetitions of '?'
local max = _soft and 100 or 2000
local t = {}
for i = 1,max do t[i] = string.rep("?", i%10 + 1) end
t[#t + 1] = ";" -- empty template
local path = table.concat(t, ";")
-- use that path in a search
local s, err = package.searchpath("xuxu", path)
-- search fails; check that message has an occurence of
-- '??????????' with ? replaced by xuxu and at least 'max' lines
assert(not s and
string.find(err, string.rep("xuxu", 10)) and
#string.gsub(err, "[^\n]", "") >= max)
-- path with one very long template
local path = string.rep("?", max)
local s, err = package.searchpath("xuxu", path)
assert(not s and string.find(err, string.rep('xuxu', max)))
end
do
local oldpath = package.path
package.path = {}
local s, err = pcall(require, "no-such-file")
assert(not s and string.find(err, "package.path"))
package.path = oldpath
end
print('+')
-- The next tests for 'require' assume some specific directories and
-- libraries.
if not _port then --[
local dirsep = string.match(package.config, "^([^\n]+)\n")
-- auxiliary directory with C modules and temporary files
local DIR = "libs" .. dirsep
-- prepend DIR to a name and correct directory separators
local function D (x)
x = string.gsub(x, "/", dirsep)
return DIR .. x
end
-- prepend DIR and pospend proper C lib. extension to a name
local function DC (x)
local ext = (dirsep == '\\') and ".dll" or ".so"
return D(x .. ext)
end
local function createfiles (files, preextras, posextras)
for n,c in pairs(files) do
io.output(D(n))
io.write(string.format(preextras, n))
io.write(c)
io.write(string.format(posextras, n))
io.close(io.output())
end
end
function removefiles (files)
for n in pairs(files) do
os.remove(D(n))
end
end
local files = {
["names.lua"] = "do return {...} end\n",
["err.lua"] = "B = 15; a = a + 1;",
["synerr.lua"] = "B =",
["A.lua"] = "",
["B.lua"] = "assert(...=='B');require 'A'",
["A.lc"] = "",
["A"] = "",
["L"] = "",
["XXxX"] = "",
["C.lua"] = "package.loaded[...] = 25; require'C'",
}
AA = nil
local extras = [[
NAME = '%s'
REQUIRED = ...
return AA]]
createfiles(files, "", extras)
-- testing explicit "dir" separator in 'searchpath'
assert(package.searchpath("C.lua", D"?", "", "") == D"C.lua")
assert(package.searchpath("C.lua", D"?", ".", ".") == D"C.lua")
assert(package.searchpath("--x-", D"?", "-", "X") == D"XXxX")
assert(package.searchpath("---xX", D"?", "---", "XX") == D"XXxX")
assert(package.searchpath(D"C.lua", "?", dirsep) == D"C.lua")
assert(package.searchpath(".\\C.lua", D"?", "\\") == D"./C.lua")
local oldpath = package.path
package.path = string.gsub("D/?.lua;D/?.lc;D/?;D/??x?;D/L", "D/", DIR)
local try = function (p, n, r)
NAME = nil
local rr = require(p)
assert(NAME == n)
assert(REQUIRED == p)
assert(rr == r)
end
a = require"names"
assert(a[1] == "names" and a[2] == D"names.lua")
_G.a = nil
local st, msg = pcall(require, "err")
assert(not st and string.find(msg, "arithmetic") and B == 15)
st, msg = pcall(require, "synerr")
assert(not st and string.find(msg, "error loading module"))
assert(package.searchpath("C", package.path) == D"C.lua")
assert(require"C" == 25)
assert(require"C" == 25)
AA = nil
try('B', 'B.lua', true)
assert(package.loaded.B)
assert(require"B" == true)
assert(package.loaded.A)
assert(require"C" == 25)
package.loaded.A = nil
try('B', nil, true) -- should not reload package
try('A', 'A.lua', true)
package.loaded.A = nil
os.remove(D'A.lua')
AA = {}
try('A', 'A.lc', AA) -- now must find second option
assert(package.searchpath("A", package.path) == D"A.lc")
assert(require("A") == AA)
AA = false
try('K', 'L', false) -- default option
try('K', 'L', false) -- default option (should reload it)
assert(rawget(_G, "_REQUIREDNAME") == nil)
AA = "x"
try("X", "XXxX", AA)
removefiles(files)
-- testing require of sub-packages
local _G = _G
package.path = string.gsub("D/?.lua;D/?/init.lua", "D/", DIR)
files = {
["P1/init.lua"] = "AA = 10",
["P1/xuxu.lua"] = "AA = 20",
}
createfiles(files, "_ENV = {}\n", "\nreturn _ENV\n")
AA = 0
local m = assert(require"P1")
assert(AA == 0 and m.AA == 10)
assert(require"P1" == m)
assert(require"P1" == m)
assert(package.searchpath("P1.xuxu", package.path) == D"P1/xuxu.lua")
m.xuxu = assert(require"P1.xuxu")
assert(AA == 0 and m.xuxu.AA == 20)
assert(require"P1.xuxu" == m.xuxu)
assert(require"P1.xuxu" == m.xuxu)
assert(require"P1" == m and m.AA == 10)
removefiles(files)
package.path = ""
assert(not pcall(require, "file_does_not_exist"))
package.path = "??\0?"
assert(not pcall(require, "file_does_not_exist1"))
package.path = oldpath
-- check 'require' error message
local fname = "file_does_not_exist2"
local m, err = pcall(require, fname)
for t in string.gmatch(package.path..";"..package.cpath, "[^;]+") do
t = string.gsub(t, "?", fname)
assert(string.find(err, t, 1, true))
end
do -- testing 'package.searchers' not being a table
local searchers = package.searchers
package.searchers = 3
local st, msg = pcall(require, 'a')
assert(not st and string.find(msg, "must be a table"))
package.searchers = searchers
end
local function import(...)
local f = {...}
return function (m)
for i=1, #f do m[f[i]] = _G[f[i]] end
end
end
-- cannot change environment of a C function
assert(not pcall(module, 'XUXU'))
-- testing require of C libraries
local p = "" -- On Mac OS X, redefine this to "_"
-- check whether loadlib works in this system
local st, err, when = package.loadlib(DC"lib1", "*")
if not st then
local f, err, when = package.loadlib("donotexist", p.."xuxu")
assert(not f and type(err) == "string" and when == "absent")
;(Message or print)('\n >>> cannot load dynamic library <<<\n')
print(err, when)
else
-- tests for loadlib
local f = assert(package.loadlib(DC"lib1", p.."onefunction"))
local a, b = f(15, 25)
assert(a == 25 and b == 15)
f = assert(package.loadlib(DC"lib1", p.."anotherfunc"))
assert(f(10, 20) == "10%20\n")
-- check error messages
local f, err, when = package.loadlib(DC"lib1", p.."xuxu")
assert(not f and type(err) == "string" and when == "init")
f, err, when = package.loadlib("donotexist", p.."xuxu")
assert(not f and type(err) == "string" and when == "open")
-- symbols from 'lib1' must be visible to other libraries
f = assert(package.loadlib(DC"lib11", p.."luaopen_lib11"))
assert(f() == "exported")
-- test C modules with prefixes in names
package.cpath = DC"?"
local lib2 = require"lib2-v2"
-- check correct access to global environment and correct
-- parameters
assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2")
assert(lib2.id("x") == "x")
-- test C submodules
local fs = require"lib1.sub"
assert(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1")
assert(fs.id(45) == 45)
end
_ENV = _G
-- testing preload
do
local p = package
package = {}
p.preload.pl = function (...)
local _ENV = {...}
function xuxu (x) return x+20 end
return _ENV
end
local pl = require"pl"
assert(require"pl" == pl)
assert(pl.xuxu(10) == 30)
assert(pl[1] == "pl" and pl[2] == nil)
package = p
assert(type(package.path) == "string")
end
print('+')
end --]
print("testing assignments, logical operators, and constructors")
local res, res2 = 27
a, b = 1, 2+3
assert(a==1 and b==5)
a={}
function f() return 10, 11, 12 end
a.x, b, a[1] = 1, 2, f()
assert(a.x==1 and b==2 and a[1]==10)
a[f()], b, a[f()+3] = f(), a, 'x'
assert(a[10] == 10 and b == a and a[13] == 'x')
do
local f = function (n) local x = {}; for i=1,n do x[i]=i end;
return table.unpack(x) end;
local a,b,c
a,b = 0, f(1)
assert(a == 0 and b == 1)
A,b = 0, f(1)
assert(A == 0 and b == 1)
a,b,c = 0,5,f(4)
assert(a==0 and b==5 and c==1)
a,b,c = 0,5,f(0)
assert(a==0 and b==5 and c==nil)
end
a, b, c, d = 1 and nil, 1 or nil, (1 and (nil or 1)), 6
assert(not a and b and c and d==6)
d = 20
a, b, c, d = f()
assert(a==10 and b==11 and c==12 and d==nil)
a,b = f(), 1, 2, 3, f()
assert(a==10 and b==1)
assert(a<b == false and a>b == true)
assert((10 and 2) == 2)
assert((10 or 2) == 10)
assert((10 or assert(nil)) == 10)
assert(not (nil and assert(nil)))
assert((nil or "alo") == "alo")
assert((nil and 10) == nil)
assert((false and 10) == false)
assert((true or 10) == true)
assert((false or 10) == 10)
assert(false ~= nil)
assert(nil ~= false)
assert(not nil == true)
assert(not not nil == false)
assert(not not 1 == true)
assert(not not a == true)
assert(not not (6 or nil) == true)
assert(not not (nil and 56) == false)
assert(not not (nil and true) == false)
assert(not 10 == false)
assert(not {} == false)
assert(not 0.5 == false)
assert(not "x" == false)
assert({} ~= {})
print('+')
a = {}
a[true] = 20
a[false] = 10
assert(a[1<2] == 20 and a[1>2] == 10)
function f(a) return a end
local a = {}
for i=3000,-3000,-1 do a[i + 0.0] = i; end
a[10e30] = "alo"; a[true] = 10; a[false] = 20
assert(a[10e30] == 'alo' and a[not 1] == 20 and a[10<20] == 10)
for i=3000,-3000,-1 do assert(a[i] == i); end
a[print] = assert
a[f] = print
a[a] = a
assert(a[a][a][a][a][print] == assert)
a[print](a[a[f]] == a[print])
assert(not pcall(function () local a = {}; a[nil] = 10 end))
assert(not pcall(function () local a = {[nil] = 10} end))
assert(a[nil] == nil)
a = nil
a = {10,9,8,7,6,5,4,3,2; [-3]='a', [f]=print, a='a', b='ab'}
a, a.x, a.y = a, a[-3]
assert(a[1]==10 and a[-3]==a.a and a[f]==print and a.x=='a' and not a.y)
a[1], f(a)[2], b, c = {['alo']=assert}, 10, a[1], a[f], 6, 10, 23, f(a), 2
a[1].alo(a[2]==10 and b==10 and c==print)
-- test of large float/integer indices
-- compute maximum integer where all bits fit in a float
local maxint = math.maxinteger
while maxint - 1.0 == maxint - 0.0 do -- trim (if needed) to fit in a float
maxint = maxint // 2
end
maxintF = maxint + 0.0 -- float version
assert(math.type(maxintF) == "float" and maxintF >= 2.0^14)
-- floats and integers must index the same places
a[maxintF] = 10; a[maxintF - 1.0] = 11;
a[-maxintF] = 12; a[-maxintF + 1.0] = 13;
assert(a[maxint] == 10 and a[maxint - 1] == 11 and
a[-maxint] == 12 and a[-maxint + 1] == 13)
a[maxint] = 20
a[-maxint] = 22
assert(a[maxintF] == 20 and a[maxintF - 1.0] == 11 and
a[-maxintF] == 22 and a[-maxintF + 1.0] == 13)
a = nil
-- test conflicts in multiple assignment
do
local a,i,j,b
a = {'a', 'b'}; i=1; j=2; b=a
i, a[i], a, j, a[j], a[i+j] = j, i, i, b, j, i
assert(i == 2 and b[1] == 1 and a == 1 and j == b and b[2] == 2 and
b[3] == 1)
end
-- repeat test with upvalues
do
local a,i,j,b
a = {'a', 'b'}; i=1; j=2; b=a
local function foo ()
i, a[i], a, j, a[j], a[i+j] = j, i, i, b, j, i
end
foo()
assert(i == 2 and b[1] == 1 and a == 1 and j == b and b[2] == 2 and
b[3] == 1)
local t = {}
(function (a) t[a], a = 10, 20 end)(1);
assert(t[1] == 10)
end
-- bug in 5.2 beta
local function foo ()
local a
return function ()
local b
a, b = 3, 14 -- local and upvalue have same index
return a, b
end
end
local a, b = foo()()
assert(a == 3 and b == 14)
print('OK')
return res

@ -0,0 +1,82 @@
-- $Id: big.lua,v 1.32 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
if _soft then
return 'a'
end
print "testing large tables"
local debug = require"debug"
local lim = 2^18 + 1000
local prog = { "local y = {0" }
for i = 1, lim do prog[#prog + 1] = i end
prog[#prog + 1] = "}\n"
prog[#prog + 1] = "X = y\n"
prog[#prog + 1] = ("assert(X[%d] == %d)"):format(lim - 1, lim - 2)
prog[#prog + 1] = "return 0"
prog = table.concat(prog, ";")
local env = {string = string, assert = assert}
local f = assert(load(prog, nil, nil, env))
f()
assert(env.X[lim] == lim - 1 and env.X[lim + 1] == lim)
for k in pairs(env) do env[k] = nil end
-- yields during accesses larger than K (in RK)
setmetatable(env, {
__index = function (t, n) coroutine.yield('g'); return _G[n] end,
__newindex = function (t, n, v) coroutine.yield('s'); _G[n] = v end,
})
X = nil
co = coroutine.wrap(f)
assert(co() == 's')
assert(co() == 'g')
assert(co() == 'g')
assert(co() == 0)
assert(X[lim] == lim - 1 and X[lim + 1] == lim)
-- errors in accesses larger than K (in RK)
getmetatable(env).__index = function () end
getmetatable(env).__newindex = function () end
local e, m = pcall(f)
assert(not e and m:find("global 'X'"))
-- errors in metamethods
getmetatable(env).__newindex = function () error("hi") end
local e, m = xpcall(f, debug.traceback)
assert(not e and m:find("'__newindex'"))
f, X = nil
coroutine.yield'b'
if 2^32 == 0 then -- (small integers) {
print "testing string length overflow"
local repstrings = 192 -- number of strings to be concatenated
local ssize = math.ceil(2.0^32 / repstrings) + 1 -- size of each string
assert(repstrings * ssize > 2.0^32) -- it should be larger than maximum size
local longs = string.rep("\0", ssize) -- create one long string
-- create function to concatentate 'repstrings' copies of its argument
local rep = assert(load(
"local a = ...; return " .. string.rep("a", repstrings, "..")))
local a, b = pcall(rep, longs) -- call that function
-- it should fail without creating string (result would be too large)
assert(not a and string.find(b, "overflow"))
end -- }
print'OK'
return 'a'

@ -0,0 +1,328 @@
-- $Id: bitwise.lua,v 1.26 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print("testing bitwise operations")
local numbits = string.packsize('j') * 8
assert(~0 == -1)
assert((1 << (numbits - 1)) == math.mininteger)
-- basic tests for bitwise operators;
-- use variables to avoid constant folding
local a, b, c, d
a = 0xFFFFFFFFFFFFFFFF
assert(a == -1 and a & -1 == a and a & 35 == 35)
a = 0xF0F0F0F0F0F0F0F0
assert(a | -1 == -1)
assert(a ~ a == 0 and a ~ 0 == a and a ~ ~a == -1)
assert(a >> 4 == ~a)
a = 0xF0; b = 0xCC; c = 0xAA; d = 0xFD
assert(a | b ~ c & d == 0xF4)
a = 0xF0.0; b = 0xCC.0; c = "0xAA.0"; d = "0xFD.0"
assert(a | b ~ c & d == 0xF4)
a = 0xF0000000; b = 0xCC000000;
c = 0xAA000000; d = 0xFD000000
assert(a | b ~ c & d == 0xF4000000)
assert(~~a == a and ~a == -1 ~ a and -d == ~d + 1)
a = a << 32
b = b << 32
c = c << 32
d = d << 32
assert(a | b ~ c & d == 0xF4000000 << 32)
assert(~~a == a and ~a == -1 ~ a and -d == ~d + 1)
assert(-1 >> 1 == (1 << (numbits - 1)) - 1 and 1 << 31 == 0x80000000)
assert(-1 >> (numbits - 1) == 1)
assert(-1 >> numbits == 0 and
-1 >> -numbits == 0 and
-1 << numbits == 0 and
-1 << -numbits == 0)
assert((2^30 - 1) << 2^30 == 0)
assert((2^30 - 1) >> 2^30 == 0)
assert(1 >> -3 == 1 << 3 and 1000 >> 5 == 1000 << -5)
-- coercion from strings to integers
assert("0xffffffffffffffff" | 0 == -1)
assert("0xfffffffffffffffe" & "-1" == -2)
assert(" \t-0xfffffffffffffffe\n\t" & "-1" == 2)
assert(" \n -45 \t " >> " -2 " == -45 * 4)
-- out of range number
assert(not pcall(function () return "0xffffffffffffffff.0" | 0 end))
-- embedded zeros
assert(not pcall(function () return "0xffffffffffffffff\0" | 0 end))
print'+'
package.preload.bit32 = function () --{
-- no built-in 'bit32' library: implement it using bitwise operators
local bit = {}
function bit.bnot (a)
return ~a & 0xFFFFFFFF
end
--
-- in all vararg functions, avoid creating 'arg' table when there are
-- only 2 (or less) parameters, as 2 parameters is the common case
--
function bit.band (x, y, z, ...)
if not z then
return ((x or -1) & (y or -1)) & 0xFFFFFFFF
else
local arg = {...}
local res = x & y & z
for i = 1, #arg do res = res & arg[i] end
return res & 0xFFFFFFFF
end
end
function bit.bor (x, y, z, ...)
if not z then
return ((x or 0) | (y or 0)) & 0xFFFFFFFF
else
local arg = {...}
local res = x | y | z
for i = 1, #arg do res = res | arg[i] end
return res & 0xFFFFFFFF
end
end
function bit.bxor (x, y, z, ...)
if not z then
return ((x or 0) ~ (y or 0)) & 0xFFFFFFFF
else
local arg = {...}
local res = x ~ y ~ z
for i = 1, #arg do res = res ~ arg[i] end
return res & 0xFFFFFFFF
end
end
function bit.btest (...)
return bit.band(...) ~= 0
end
function bit.lshift (a, b)
return ((a & 0xFFFFFFFF) << b) & 0xFFFFFFFF
end
function bit.rshift (a, b)
return ((a & 0xFFFFFFFF) >> b) & 0xFFFFFFFF
end
function bit.arshift (a, b)
a = a & 0xFFFFFFFF
if b <= 0 or (a & 0x80000000) == 0 then
return (a >> b) & 0xFFFFFFFF
else
return ((a >> b) | ~(0xFFFFFFFF >> b)) & 0xFFFFFFFF
end
end
function bit.lrotate (a ,b)
b = b & 31
a = a & 0xFFFFFFFF
a = (a << b) | (a >> (32 - b))
return a & 0xFFFFFFFF
end
function bit.rrotate (a, b)
return bit.lrotate(a, -b)
end
local function checkfield (f, w)
w = w or 1
assert(f >= 0, "field cannot be negative")
assert(w > 0, "width must be positive")
assert(f + w <= 32, "trying to access non-existent bits")
return f, ~(-1 << w)
end
function bit.extract (a, f, w)
local f, mask = checkfield(f, w)
return (a >> f) & mask
end
function bit.replace (a, v, f, w)
local f, mask = checkfield(f, w)
v = v & mask
a = (a & ~(mask << f)) | (v << f)
return a & 0xFFFFFFFF
end
return bit
end --}
print("testing bitwise library")
local bit32 = require'bit32'
assert(bit32.band() == bit32.bnot(0))
assert(bit32.btest() == true)
assert(bit32.bor() == 0)
assert(bit32.bxor() == 0)
assert(bit32.band() == bit32.band(0xffffffff))
assert(bit32.band(1,2) == 0)
-- out-of-range numbers
assert(bit32.band(-1) == 0xffffffff)
assert(bit32.band((1 << 33) - 1) == 0xffffffff)
assert(bit32.band(-(1 << 33) - 1) == 0xffffffff)
assert(bit32.band((1 << 33) + 1) == 1)
assert(bit32.band(-(1 << 33) + 1) == 1)
assert(bit32.band(-(1 << 40)) == 0)
assert(bit32.band(1 << 40) == 0)
assert(bit32.band(-(1 << 40) - 2) == 0xfffffffe)
assert(bit32.band((1 << 40) - 4) == 0xfffffffc)
assert(bit32.lrotate(0, -1) == 0)
assert(bit32.lrotate(0, 7) == 0)
assert(bit32.lrotate(0x12345678, 0) == 0x12345678)
assert(bit32.lrotate(0x12345678, 32) == 0x12345678)
assert(bit32.lrotate(0x12345678, 4) == 0x23456781)
assert(bit32.rrotate(0x12345678, -4) == 0x23456781)
assert(bit32.lrotate(0x12345678, -8) == 0x78123456)
assert(bit32.rrotate(0x12345678, 8) == 0x78123456)
assert(bit32.lrotate(0xaaaaaaaa, 2) == 0xaaaaaaaa)
assert(bit32.lrotate(0xaaaaaaaa, -2) == 0xaaaaaaaa)
for i = -50, 50 do
assert(bit32.lrotate(0x89abcdef, i) == bit32.lrotate(0x89abcdef, i%32))
end
assert(bit32.lshift(0x12345678, 4) == 0x23456780)
assert(bit32.lshift(0x12345678, 8) == 0x34567800)
assert(bit32.lshift(0x12345678, -4) == 0x01234567)
assert(bit32.lshift(0x12345678, -8) == 0x00123456)
assert(bit32.lshift(0x12345678, 32) == 0)
assert(bit32.lshift(0x12345678, -32) == 0)
assert(bit32.rshift(0x12345678, 4) == 0x01234567)
assert(bit32.rshift(0x12345678, 8) == 0x00123456)
assert(bit32.rshift(0x12345678, 32) == 0)
assert(bit32.rshift(0x12345678, -32) == 0)
assert(bit32.arshift(0x12345678, 0) == 0x12345678)
assert(bit32.arshift(0x12345678, 1) == 0x12345678 // 2)
assert(bit32.arshift(0x12345678, -1) == 0x12345678 * 2)
assert(bit32.arshift(-1, 1) == 0xffffffff)
assert(bit32.arshift(-1, 24) == 0xffffffff)
assert(bit32.arshift(-1, 32) == 0xffffffff)
assert(bit32.arshift(-1, -1) == bit32.band(-1 * 2, 0xffffffff))
assert(0x12345678 << 4 == 0x123456780)
assert(0x12345678 << 8 == 0x1234567800)
assert(0x12345678 << -4 == 0x01234567)
assert(0x12345678 << -8 == 0x00123456)
assert(0x12345678 << 32 == 0x1234567800000000)
assert(0x12345678 << -32 == 0)
assert(0x12345678 >> 4 == 0x01234567)
assert(0x12345678 >> 8 == 0x00123456)
assert(0x12345678 >> 32 == 0)
assert(0x12345678 >> -32 == 0x1234567800000000)
print("+")
-- some special cases
local c = {0, 1, 2, 3, 10, 0x80000000, 0xaaaaaaaa, 0x55555555,
0xffffffff, 0x7fffffff}
for _, b in pairs(c) do
assert(bit32.band(b) == b)
assert(bit32.band(b, b) == b)
assert(bit32.band(b, b, b, b) == b)
assert(bit32.btest(b, b) == (b ~= 0))
assert(bit32.band(b, b, b) == b)
assert(bit32.band(b, b, b, ~b) == 0)
assert(bit32.btest(b, b, b) == (b ~= 0))
assert(bit32.band(b, bit32.bnot(b)) == 0)
assert(bit32.bor(b, bit32.bnot(b)) == bit32.bnot(0))
assert(bit32.bor(b) == b)
assert(bit32.bor(b, b) == b)
assert(bit32.bor(b, b, b) == b)
assert(bit32.bor(b, b, 0, ~b) == 0xffffffff)
assert(bit32.bxor(b) == b)
assert(bit32.bxor(b, b) == 0)
assert(bit32.bxor(b, b, b) == b)
assert(bit32.bxor(b, b, b, b) == 0)
assert(bit32.bxor(b, 0) == b)
assert(bit32.bnot(b) ~= b)
assert(bit32.bnot(bit32.bnot(b)) == b)
assert(bit32.bnot(b) == (1 << 32) - 1 - b)
assert(bit32.lrotate(b, 32) == b)
assert(bit32.rrotate(b, 32) == b)
assert(bit32.lshift(bit32.lshift(b, -4), 4) == bit32.band(b, bit32.bnot(0xf)))
assert(bit32.rshift(bit32.rshift(b, 4), -4) == bit32.band(b, bit32.bnot(0xf)))
end
-- for this test, use at most 24 bits (mantissa of a single float)
c = {0, 1, 2, 3, 10, 0x800000, 0xaaaaaa, 0x555555, 0xffffff, 0x7fffff}
for _, b in pairs(c) do
for i = -40, 40 do
local x = bit32.lshift(b, i)
local y = math.floor(math.fmod(b * 2.0^i, 2.0^32))
assert(math.fmod(x - y, 2.0^32) == 0)
end
end
assert(not pcall(bit32.band, {}))
assert(not pcall(bit32.bnot, "a"))
assert(not pcall(bit32.lshift, 45))
assert(not pcall(bit32.lshift, 45, print))
assert(not pcall(bit32.rshift, 45, print))
print("+")
-- testing extract/replace
assert(bit32.extract(0x12345678, 0, 4) == 8)
assert(bit32.extract(0x12345678, 4, 4) == 7)
assert(bit32.extract(0xa0001111, 28, 4) == 0xa)
assert(bit32.extract(0xa0001111, 31, 1) == 1)
assert(bit32.extract(0x50000111, 31, 1) == 0)
assert(bit32.extract(0xf2345679, 0, 32) == 0xf2345679)
assert(not pcall(bit32.extract, 0, -1))
assert(not pcall(bit32.extract, 0, 32))
assert(not pcall(bit32.extract, 0, 0, 33))
assert(not pcall(bit32.extract, 0, 31, 2))
assert(bit32.replace(0x12345678, 5, 28, 4) == 0x52345678)
assert(bit32.replace(0x12345678, 0x87654321, 0, 32) == 0x87654321)
assert(bit32.replace(0, 1, 2) == 2^2)
assert(bit32.replace(0, -1, 4) == 2^4)
assert(bit32.replace(-1, 0, 31) == (1 << 31) - 1)
assert(bit32.replace(-1, 0, 1, 2) == (1 << 32) - 7)
-- testing conversion of floats
assert(bit32.bor(3.0) == 3)
assert(bit32.bor(-4.0) == 0xfffffffc)
-- large floats and large-enough integers?
if 2.0^50 < 2.0^50 + 1.0 and 2.0^50 < (-1 >> 1) then
assert(bit32.bor(2.0^32 - 5.0) == 0xfffffffb)
assert(bit32.bor(-2.0^32 - 6.0) == 0xfffffffa)
assert(bit32.bor(2.0^48 - 5.0) == 0xfffffffb)
assert(bit32.bor(-2.0^48 - 6.0) == 0xfffffffa)
end
print'OK'

@ -0,0 +1,78 @@
local tonumber, tointeger = tonumber, math.tointeger
local type, getmetatable, rawget, error = type, getmetatable, rawget, error
local strsub = string.sub
local print = print
_ENV = nil
-- Try to convert a value to an integer, without assuming any coercion.
local function toint (x)
x = tonumber(x) -- handle numerical strings
if not x then
return false -- not coercible to a number
end
return tointeger(x)
end
-- If operation fails, maybe second operand has a metamethod that should
-- have been called if not for this string metamethod, so try to
-- call it.
local function trymt (x, y, mtname)
if type(y) ~= "string" then -- avoid recalling original metamethod
local mt = getmetatable(y)
local mm = mt and rawget(mt, mtname)
if mm then
return mm(x, y)
end
end
-- if any test fails, there is no other metamethod to be called
error("attempt to '" .. strsub(mtname, 3) ..
"' a " .. type(x) .. " with a " .. type(y), 4)
end
local function checkargs (x, y, mtname)
local xi = toint(x)
local yi = toint(y)
if xi and yi then
return xi, yi
else
return trymt(x, y, mtname), nil
end
end
local smt = getmetatable("")
smt.__band = function (x, y)
local x, y = checkargs(x, y, "__band")
return y and x & y or x
end
smt.__bor = function (x, y)
local x, y = checkargs(x, y, "__bor")
return y and x | y or x
end
smt.__bxor = function (x, y)
local x, y = checkargs(x, y, "__bxor")
return y and x ~ y or x
end
smt.__shl = function (x, y)
local x, y = checkargs(x, y, "__shl")
return y and x << y or x
end
smt.__shr = function (x, y)
local x, y = checkargs(x, y, "__shr")
return y and x >> y or x
end
smt.__bnot = function (x)
local x, y = checkargs(x, x, "__bnot")
return y and ~x or x
end

@ -0,0 +1,401 @@
-- $Id: calls.lua,v 1.60 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print("testing functions and calls")
local debug = require "debug"
-- get the opportunity to test 'type' too ;)
assert(type(1<2) == 'boolean')
assert(type(true) == 'boolean' and type(false) == 'boolean')
assert(type(nil) == 'nil'
and type(-3) == 'number'
and type'x' == 'string'
and type{} == 'table'
and type(type) == 'function')
assert(type(assert) == type(print))
function f (x) return a:x (x) end
assert(type(f) == 'function')
assert(not pcall(type))
do -- test error in 'print' too...
local tostring = _ENV.tostring
_ENV.tostring = nil
local st, msg = pcall(print, 1)
assert(st == false and string.find(msg, "attempt to call a nil value"))
_ENV.tostring = function () return {} end
local st, msg = pcall(print, 1)
assert(st == false and string.find(msg, "must return a string"))
_ENV.tostring = tostring
end
-- testing local-function recursion
fact = false
do
local res = 1
local function fact (n)
if n==0 then return res
else return n*fact(n-1)
end
end
assert(fact(5) == 120)
end
assert(fact == false)
-- testing declarations
a = {i = 10}
self = 20
function a:x (x) return x+self.i end
function a.y (x) return x+self end
assert(a:x(1)+10 == a.y(1))
a.t = {i=-100}
a["t"].x = function (self, a,b) return self.i+a+b end
assert(a.t:x(2,3) == -95)
do
local a = {x=0}
function a:add (x) self.x, a.y = self.x+x, 20; return self end
assert(a:add(10):add(20):add(30).x == 60 and a.y == 20)
end
local a = {b={c={}}}
function a.b.c.f1 (x) return x+1 end
function a.b.c:f2 (x,y) self[x] = y end
assert(a.b.c.f1(4) == 5)
a.b.c:f2('k', 12); assert(a.b.c.k == 12)
print('+')
t = nil -- 'declare' t
function f(a,b,c) local d = 'a'; t={a,b,c,d} end
f( -- this line change must be valid
1,2)
assert(t[1] == 1 and t[2] == 2 and t[3] == nil and t[4] == 'a')
f(1,2, -- this one too
3,4)
assert(t[1] == 1 and t[2] == 2 and t[3] == 3 and t[4] == 'a')
function fat(x)
if x <= 1 then return 1
else return x*load("return fat(" .. x-1 .. ")", "")()
end
end
assert(load "load 'assert(fat(6)==720)' () ")()
a = load('return fat(5), 3')
a,b = a()
assert(a == 120 and b == 3)
print('+')
function err_on_n (n)
if n==0 then error(); exit(1);
else err_on_n (n-1); exit(1);
end
end
do
function dummy (n)
if n > 0 then
assert(not pcall(err_on_n, n))
dummy(n-1)
end
end
end
dummy(10)
function deep (n)
if n>0 then deep(n-1) end
end
deep(10)
deep(200)
-- testing tail call
function deep (n) if n>0 then return deep(n-1) else return 101 end end
assert(deep(30000) == 101)
a = {}
function a:deep (n) if n>0 then return self:deep(n-1) else return 101 end end
assert(a:deep(30000) == 101)
print('+')
a = nil
(function (x) a=x end)(23)
assert(a == 23 and (function (x) return x*2 end)(20) == 40)
-- testing closures
-- fixed-point operator
Z = function (le)
local function a (f)
return le(function (x) return f(f)(x) end)
end
return a(a)
end
-- non-recursive factorial
F = function (f)
return function (n)
if n == 0 then return 1
else return n*f(n-1) end
end
end
fat = Z(F)
assert(fat(0) == 1 and fat(4) == 24 and Z(F)(5)==5*Z(F)(4))
local function g (z)
local function f (a,b,c,d)
return function (x,y) return a+b+c+d+a+x+y+z end
end
return f(z,z+1,z+2,z+3)
end
f = g(10)
assert(f(9, 16) == 10+11+12+13+10+9+16+10)
Z, F, f = nil
print('+')
-- testing multiple returns
function unlpack (t, i)
i = i or 1
if (i <= #t) then
return t[i], unlpack(t, i+1)
end
end
function equaltab (t1, t2)
assert(#t1 == #t2)
for i = 1, #t1 do
assert(t1[i] == t2[i])
end
end
local pack = function (...) return (table.pack(...)) end
function f() return 1,2,30,4 end
function ret2 (a,b) return a,b end
local a,b,c,d = unlpack{1,2,3}
assert(a==1 and b==2 and c==3 and d==nil)
a = {1,2,3,4,false,10,'alo',false,assert}
equaltab(pack(unlpack(a)), a)
equaltab(pack(unlpack(a), -1), {1,-1})
a,b,c,d = ret2(f()), ret2(f())
assert(a==1 and b==1 and c==2 and d==nil)
a,b,c,d = unlpack(pack(ret2(f()), ret2(f())))
assert(a==1 and b==1 and c==2 and d==nil)
a,b,c,d = unlpack(pack(ret2(f()), (ret2(f()))))
assert(a==1 and b==1 and c==nil and d==nil)
a = ret2{ unlpack{1,2,3}, unlpack{3,2,1}, unlpack{"a", "b"}}
assert(a[1] == 1 and a[2] == 3 and a[3] == "a" and a[4] == "b")
-- testing calls with 'incorrect' arguments
rawget({}, "x", 1)
rawset({}, "x", 1, 2)
assert(math.sin(1,2) == math.sin(1))
table.sort({10,9,8,4,19,23,0,0}, function (a,b) return a<b end, "extra arg")
-- test for generic load
local x = "-- a comment\0\0\0\n x = 10 + \n23; \
local a = function () x = 'hi' end; \
return '\0'"
function read1 (x)
local i = 0
return function ()
collectgarbage()
i=i+1
return string.sub(x, i, i)
end
end
function cannotload (msg, a,b)
assert(not a and string.find(b, msg))
end
a = assert(load(read1(x), "modname", "t", _G))
assert(a() == "\0" and _G.x == 33)
assert(debug.getinfo(a).source == "modname")
-- cannot read text in binary mode
cannotload("attempt to load a text chunk", load(read1(x), "modname", "b", {}))
cannotload("attempt to load a text chunk", load(x, "modname", "b"))
a = assert(load(function () return nil end))
a() -- empty chunk
assert(not load(function () return true end))
-- small bug
local t = {nil, "return ", "3"}
f, msg = load(function () return table.remove(t, 1) end)
assert(f() == nil) -- should read the empty chunk
-- another small bug (in 5.2.1)
f = load(string.dump(function () return 1 end), nil, "b", {})
assert(type(f) == "function" and f() == 1)
x = string.dump(load("x = 1; return x"))
a = assert(load(read1(x), nil, "b"))
assert(a() == 1 and _G.x == 1)
cannotload("attempt to load a binary chunk", load(read1(x), nil, "t"))
cannotload("attempt to load a binary chunk", load(x, nil, "t"))
assert(not pcall(string.dump, print)) -- no dump of C functions
cannotload("unexpected symbol", load(read1("*a = 123")))
cannotload("unexpected symbol", load("*a = 123"))
cannotload("hhi", load(function () error("hhi") end))
-- any value is valid for _ENV
assert(load("return _ENV", nil, nil, 123)() == 123)
-- load when _ENV is not first upvalue
local x; XX = 123
local function h ()
local y=x -- use 'x', so that it becomes 1st upvalue
return XX -- global name
end
local d = string.dump(h)
x = load(d, "", "b")
assert(debug.getupvalue(x, 2) == '_ENV')
debug.setupvalue(x, 2, _G)
assert(x() == 123)
assert(assert(load("return XX + ...", nil, nil, {XX = 13}))(4) == 17)
-- test generic load with nested functions
x = [[
return function (x)
return function (y)
return function (z)
return x+y+z
end
end
end
]]
a = assert(load(read1(x)))
assert(a()(2)(3)(10) == 15)
-- test for dump/undump with upvalues
local a, b = 20, 30
x = load(string.dump(function (x)
if x == "set" then a = 10+b; b = b+1 else
return a
end
end), "", "b", nil)
assert(x() == nil)
assert(debug.setupvalue(x, 1, "hi") == "a")
assert(x() == "hi")
assert(debug.setupvalue(x, 2, 13) == "b")
assert(not debug.setupvalue(x, 3, 10)) -- only 2 upvalues
x("set")
assert(x() == 23)
x("set")
assert(x() == 24)
-- test for dump/undump with many upvalues
do
local nup = 200 -- maximum number of local variables
local prog = {"local a1"}
for i = 2, nup do prog[#prog + 1] = ", a" .. i end
prog[#prog + 1] = " = 1"
for i = 2, nup do prog[#prog + 1] = ", " .. i end
local sum = 1
prog[#prog + 1] = "; return function () return a1"
for i = 2, nup do prog[#prog + 1] = " + a" .. i; sum = sum + i end
prog[#prog + 1] = " end"
prog = table.concat(prog)
local f = assert(load(prog))()
assert(f() == sum)
f = load(string.dump(f)) -- main chunk now has many upvalues
local a = 10
local h = function () return a end
for i = 1, nup do
debug.upvaluejoin(f, i, h, 1)
end
assert(f() == 10 * nup)
end
-- test for long method names
do
local t = {x = 1}
function t:_012345678901234567890123456789012345678901234567890123456789 ()
return self.x
end
assert(t:_012345678901234567890123456789012345678901234567890123456789() == 1)
end
-- test for bug in parameter adjustment
assert((function () return nil end)(4) == nil)
assert((function () local a; return a end)(4) == nil)
assert((function (a) return a end)() == nil)
print("testing binary chunks")
do
local header = string.pack("c4BBc6BBBBBj",
"\27Lua", -- signature
5*16 + 3, -- version 5.3
0, -- format
"\x19\x93\r\n\x1a\n", -- data
string.packsize("i"), -- sizeof(int)
string.packsize("T"), -- sizeof(size_t)
4, -- size of instruction
string.packsize("j"), -- sizeof(lua integer)
string.packsize("n"), -- sizeof(lua number)
0x5678 -- LUAC_INT
-- LUAC_NUM may not have a unique binary representation (padding...)
)
local c = string.dump(function () local a = 1; local b = 3; return a+b*3 end)
assert(string.sub(c, 1, #header) == header)
-- corrupted header
for i = 1, #header do
local s = string.sub(c, 1, i - 1) ..
string.char(string.byte(string.sub(c, i, i)) + 1) ..
string.sub(c, i + 1, -1)
assert(#s == #c)
assert(not load(s))
end
-- loading truncated binary chunks
for i = 1, #c - 1 do
local st, msg = load(string.sub(c, 1, i))
assert(not st and string.find(msg, "truncated"))
end
assert(assert(load(c))() == 10)
end
print('OK')
return deep

@ -0,0 +1,247 @@
-- $Id: closure.lua,v 1.59 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print "testing closures"
local A,B = 0,{g=10}
function f(x)
local a = {}
for i=1,1000 do
local y = 0
do
a[i] = function () B.g = B.g+1; y = y+x; return y+A end
end
end
local dummy = function () return a[A] end
collectgarbage()
A = 1; assert(dummy() == a[1]); A = 0;
assert(a[1]() == x)
assert(a[3]() == x)
collectgarbage()
assert(B.g == 12)
return a
end
local a = f(10)
-- force a GC in this level
local x = {[1] = {}} -- to detect a GC
setmetatable(x, {__mode = 'kv'})
while x[1] do -- repeat until GC
local a = A..A..A..A -- create garbage
A = A+1
end
assert(a[1]() == 20+A)
assert(a[1]() == 30+A)
assert(a[2]() == 10+A)
collectgarbage()
assert(a[2]() == 20+A)
assert(a[2]() == 30+A)
assert(a[3]() == 20+A)
assert(a[8]() == 10+A)
assert(getmetatable(x).__mode == 'kv')
assert(B.g == 19)
-- testing equality
a = {}
for i = 1, 5 do a[i] = function (x) return x + a + _ENV end end
assert(a[3] == a[4] and a[4] == a[5])
for i = 1, 5 do a[i] = function (x) return i + a + _ENV end end
assert(a[3] ~= a[4] and a[4] ~= a[5])
local function f()
return function (x) return math.sin(_ENV[x]) end
end
assert(f() == f())
-- testing closures with 'for' control variable
a = {}
for i=1,10 do
a[i] = {set = function(x) i=x end, get = function () return i end}
if i == 3 then break end
end
assert(a[4] == nil)
a[1].set(10)
assert(a[2].get() == 2)
a[2].set('a')
assert(a[3].get() == 3)
assert(a[2].get() == 'a')
a = {}
local t = {"a", "b"}
for i = 1, #t do
local k = t[i]
a[i] = {set = function(x, y) i=x; k=y end,
get = function () return i, k end}
if i == 2 then break end
end
a[1].set(10, 20)
local r,s = a[2].get()
assert(r == 2 and s == 'b')
r,s = a[1].get()
assert(r == 10 and s == 20)
a[2].set('a', 'b')
r,s = a[2].get()
assert(r == "a" and s == "b")
-- testing closures with 'for' control variable x break
for i=1,3 do
f = function () return i end
break
end
assert(f() == 1)
for k = 1, #t do
local v = t[k]
f = function () return k, v end
break
end
assert(({f()})[1] == 1)
assert(({f()})[2] == "a")
-- testing closure x break x return x errors
local b
function f(x)
local first = 1
while 1 do
if x == 3 and not first then return end
local a = 'xuxu'
b = function (op, y)
if op == 'set' then
a = x+y
else
return a
end
end
if x == 1 then do break end
elseif x == 2 then return
else if x ~= 3 then error() end
end
first = nil
end
end
for i=1,3 do
f(i)
assert(b('get') == 'xuxu')
b('set', 10); assert(b('get') == 10+i)
b = nil
end
pcall(f, 4);
assert(b('get') == 'xuxu')
b('set', 10); assert(b('get') == 14)
local w
-- testing multi-level closure
function f(x)
return function (y)
return function (z) return w+x+y+z end
end
end
y = f(10)
w = 1.345
assert(y(20)(30) == 60+w)
-- testing closures x repeat-until
local a = {}
local i = 1
repeat
local x = i
a[i] = function () i = x+1; return x end
until i > 10 or a[i]() ~= x
assert(i == 11 and a[1]() == 1 and a[3]() == 3 and i == 4)
-- testing closures created in 'then' and 'else' parts of 'if's
a = {}
for i = 1, 10 do
if i % 3 == 0 then
local y = 0
a[i] = function (x) local t = y; y = x; return t end
elseif i % 3 == 1 then
goto L1
error'not here'
::L1::
local y = 1
a[i] = function (x) local t = y; y = x; return t end
elseif i % 3 == 2 then
local t
goto l4
::l4a:: a[i] = t; goto l4b
error("should never be here!")
::l4::
local y = 2
t = function (x) local t = y; y = x; return t end
goto l4a
error("should never be here!")
::l4b::
end
end
for i = 1, 10 do
assert(a[i](i * 10) == i % 3 and a[i]() == i * 10)
end
print'+'
-- test for correctly closing upvalues in tail calls of vararg functions
local function t ()
local function c(a,b) assert(a=="test" and b=="OK") end
local function v(f, ...) c("test", f() ~= 1 and "FAILED" or "OK") end
local x = 1
return v(function() return x end)
end
t()
-- test for debug manipulation of upvalues
local debug = require'debug'
do
local a , b, c = 3, 5, 7
foo1 = function () return a+b end;
foo2 = function () return b+a end;
do
local a = 10
foo3 = function () return a+b end;
end
end
assert(debug.upvalueid(foo1, 1))
assert(debug.upvalueid(foo1, 2))
assert(not pcall(debug.upvalueid, foo1, 3))
assert(debug.upvalueid(foo1, 1) == debug.upvalueid(foo2, 2))
assert(debug.upvalueid(foo1, 2) == debug.upvalueid(foo2, 1))
assert(debug.upvalueid(foo3, 1))
assert(debug.upvalueid(foo1, 1) ~= debug.upvalueid(foo3, 1))
assert(debug.upvalueid(foo1, 2) == debug.upvalueid(foo3, 2))
assert(debug.upvalueid(string.gmatch("x", "x"), 1) ~= nil)
assert(foo1() == 3 + 5 and foo2() == 5 + 3)
debug.upvaluejoin(foo1, 2, foo2, 2)
assert(foo1() == 3 + 3 and foo2() == 5 + 3)
assert(foo3() == 10 + 5)
debug.upvaluejoin(foo3, 2, foo2, 1)
assert(foo3() == 10 + 5)
debug.upvaluejoin(foo3, 2, foo2, 2)
assert(foo3() == 10 + 3)
assert(not pcall(debug.upvaluejoin, foo1, 3, foo2, 1))
assert(not pcall(debug.upvaluejoin, foo1, 1, foo2, 3))
assert(not pcall(debug.upvaluejoin, foo1, 0, foo2, 1))
assert(not pcall(debug.upvaluejoin, print, 1, foo2, 1))
assert(not pcall(debug.upvaluejoin, {}, 1, foo2, 1))
assert(not pcall(debug.upvaluejoin, foo1, 1, print, 1))
print'OK'

@ -0,0 +1,239 @@
-- $Id: code.lua,v 1.42 2016/11/07 13:04:32 roberto Exp $
-- See Copyright Notice in file all.lua
if T==nil then
(Message or print)('\n >>> testC not active: skipping opcode tests <<<\n')
return
end
print "testing code generation and optimizations"
-- this code gave an error for the code checker
do
local function f (a)
for k,v,w in a do end
end
end
-- testing reuse in constant table
local function checkKlist (func, list)
local k = T.listk(func)
assert(#k == #list)
for i = 1, #k do
assert(k[i] == list[i] and math.type(k[i]) == math.type(list[i]))
end
end
local function foo ()
local a
a = 3;
a = 0; a = 0.0; a = -7 + 7
a = 3.78/4; a = 3.78/4
a = -3.78/4; a = 3.78/4; a = -3.78/4
a = -3.79/4; a = 0.0; a = -0;
a = 3; a = 3.0; a = 3; a = 3.0
end
checkKlist(foo, {3, 0, 0.0, 3.78/4, -3.78/4, -3.79/4, 3.0})
-- testing opcodes
function check (f, ...)
local arg = {...}
local c = T.listcode(f)
for i=1, #arg do
-- print(arg[i], c[i])
assert(string.find(c[i], '- '..arg[i]..' *%d'))
end
assert(c[#arg+2] == nil)
end
function checkequal (a, b)
a = T.listcode(a)
b = T.listcode(b)
for i = 1, #a do
a[i] = string.gsub(a[i], '%b()', '') -- remove line number
b[i] = string.gsub(b[i], '%b()', '') -- remove line number
assert(a[i] == b[i])
end
end
-- some basic instructions
check(function ()
(function () end){f()}
end, 'CLOSURE', 'NEWTABLE', 'GETTABUP', 'CALL', 'SETLIST', 'CALL', 'RETURN')
-- sequence of LOADNILs
check(function ()
local a,b,c
local d; local e;
local f,g,h;
d = nil; d=nil; b=nil; a=nil; c=nil;
end, 'LOADNIL', 'RETURN')
check(function ()
local a,b,c,d = 1,1,1,1
d=nil;c=nil;b=nil;a=nil
end, 'LOADK', 'LOADK', 'LOADK', 'LOADK', 'LOADNIL', 'RETURN')
do
local a,b,c,d = 1,1,1,1
d=nil;c=nil;b=nil;a=nil
assert(a == nil and b == nil and c == nil and d == nil)
end
-- single return
check (function (a,b,c) return a end, 'RETURN')
-- infinite loops
check(function () while true do local a = -1 end end,
'LOADK', 'JMP', 'RETURN')
check(function () while 1 do local a = -1 end end,
'LOADK', 'JMP', 'RETURN')
check(function () repeat local x = 1 until true end,
'LOADK', 'RETURN')
-- concat optimization
check(function (a,b,c,d) return a..b..c..d end,
'MOVE', 'MOVE', 'MOVE', 'MOVE', 'CONCAT', 'RETURN')
-- not
check(function () return not not nil end, 'LOADBOOL', 'RETURN')
check(function () return not not false end, 'LOADBOOL', 'RETURN')
check(function () return not not true end, 'LOADBOOL', 'RETURN')
check(function () return not not 1 end, 'LOADBOOL', 'RETURN')
-- direct access to locals
check(function ()
local a,b,c,d
a = b*2
c[2], a[b] = -((a + d/2 - a[b]) ^ a.x), b
end,
'LOADNIL',
'MUL',
'DIV', 'ADD', 'GETTABLE', 'SUB', 'GETTABLE', 'POW',
'UNM', 'SETTABLE', 'SETTABLE', 'RETURN')
-- direct access to constants
check(function ()
local a,b
a.x = 3.2
a.x = b
a[b] = 'x'
end,
'LOADNIL', 'SETTABLE', 'SETTABLE', 'SETTABLE', 'RETURN')
check(function ()
local a,b
a = 1 - a
b = 1/a
b = 5-4
end,
'LOADNIL', 'SUB', 'DIV', 'LOADK', 'RETURN')
check(function ()
local a,b
a[true] = false
end,
'LOADNIL', 'SETTABLE', 'RETURN')
-- constant folding
local function checkK (func, val)
check(func, 'LOADK', 'RETURN')
local k = T.listk(func)
assert(#k == 1 and k[1] == val and math.type(k[1]) == math.type(val))
assert(func() == val)
end
checkK(function () return 0.0 end, 0.0)
checkK(function () return 0 end, 0)
checkK(function () return -0//1 end, 0)
checkK(function () return 3^-1 end, 1/3)
checkK(function () return (1 + 1)^(50 + 50) end, 2^100)
checkK(function () return (-2)^(31 - 2) end, -0x20000000 + 0.0)
checkK(function () return (-3^0 + 5) // 3.0 end, 1.0)
checkK(function () return -3 % 5 end, 2)
checkK(function () return -((2.0^8 + -(-1)) % 8)/2 * 4 - 3 end, -5.0)
checkK(function () return -((2^8 + -(-1)) % 8)//2 * 4 - 3 end, -7.0)
checkK(function () return 0xF0.0 | 0xCC.0 ~ 0xAA & 0xFD end, 0xF4)
checkK(function () return ~(~0xFF0 | 0xFF0) end, 0)
checkK(function () return ~~-100024.0 end, -100024)
checkK(function () return ((100 << 6) << -4) >> 2 end, 100)
-- no foldings
check(function () return -0.0 end, 'LOADK', 'UNM', 'RETURN')
check(function () return 3/0 end, 'DIV', 'RETURN')
check(function () return 0%0 end, 'MOD', 'RETURN')
check(function () return -4//0 end, 'IDIV', 'RETURN')
-- bug in constant folding for 5.1
check(function () return -nil end, 'LOADNIL', 'UNM', 'RETURN')
check(function ()
local a,b,c
b[c], a = c, b
b[a], a = c, b
a, b = c, a
a = a
end,
'LOADNIL',
'MOVE', 'MOVE', 'SETTABLE',
'MOVE', 'MOVE', 'MOVE', 'SETTABLE',
'MOVE', 'MOVE', 'MOVE',
-- no code for a = a
'RETURN')
-- x == nil , x ~= nil
checkequal(function () if (a==nil) then a=1 end; if a~=nil then a=1 end end,
function () if (a==9) then a=1 end; if a~=9 then a=1 end end)
check(function () if a==nil then a='a' end end,
'GETTABUP', 'EQ', 'JMP', 'SETTABUP', 'RETURN')
-- de morgan
checkequal(function () local a; if not (a or b) then b=a end end,
function () local a; if (not a and not b) then b=a end end)
checkequal(function (l) local a; return 0 <= a and a <= l end,
function (l) local a; return not (not(a >= 0) or not(a <= l)) end)
-- if-goto optimizations
check(function (a, b, c, d, e)
if a == b then goto l1
elseif a == c then goto l2
elseif a == d then goto l2
else if a == e then goto l3
else goto l3
end
end
::l1:: ::l2:: ::l3:: ::l4::
end, 'EQ', 'JMP', 'EQ', 'JMP', 'EQ', 'JMP', 'EQ', 'JMP', 'JMP', 'RETURN')
checkequal(
function (a) while a < 10 do a = a + 1 end end,
function (a) ::L2:: if not(a < 10) then goto L1 end; a = a + 1;
goto L2; ::L1:: end
)
checkequal(
function (a) while a < 10 do a = a + 1 end end,
function (a) while true do if not(a < 10) then break end; a = a + 1; end end
)
print 'OK'

@ -0,0 +1,313 @@
-- $Id: constructs.lua,v 1.41 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
;;print "testing syntax";;
local debug = require "debug"
local function checkload (s, msg)
assert(string.find(select(2, load(s)), msg))
end
-- testing semicollons
do ;;; end
; do ; a = 3; assert(a == 3) end;
;
-- invalid operations should not raise errors when not executed
if false then a = 3 // 0; a = 0 % 0 end
-- testing priorities
assert(2^3^2 == 2^(3^2));
assert(2^3*4 == (2^3)*4);
assert(2.0^-2 == 1/4 and -2^- -2 == - - -4);
assert(not nil and 2 and not(2>3 or 3<2));
assert(-3-1-5 == 0+0-9);
assert(-2^2 == -4 and (-2)^2 == 4 and 2*2-3-1 == 0);
assert(-3%5 == 2 and -3+5 == 2)
assert(2*1+3/3 == 3 and 1+2 .. 3*1 == "33");
assert(not(2+1 > 3*1) and "a".."b" > "a");
assert("7" .. 3 << 1 == 146)
assert(10 >> 1 .. "9" == 0)
assert(10 | 1 .. "9" == 27)
assert(0xF0 | 0xCC ~ 0xAA & 0xFD == 0xF4)
assert(0xFD & 0xAA ~ 0xCC | 0xF0 == 0xF4)
assert(0xF0 & 0x0F + 1 == 0x10)
assert(3^4//2^3//5 == 2)
assert(-3+4*5//2^3^2//9+4%10/3 == (-3)+(((4*5)//(2^(3^2)))//9)+((4%10)/3))
assert(not ((true or false) and nil))
assert( true or false and nil)
-- old bug
assert((((1 or false) and true) or false) == true)
assert((((nil and true) or false) and true) == false)
local a,b = 1,nil;
assert(-(1 or 2) == -1 and (1 and 2)+(-1.25 or -4) == 0.75);
x = ((b or a)+1 == 2 and (10 or a)+1 == 11); assert(x);
x = (((2<3) or 1) == true and (2<3 and 4) == 4); assert(x);
x,y=1,2;
assert((x>y) and x or y == 2);
x,y=2,1;
assert((x>y) and x or y == 2);
assert(1234567890 == tonumber('1234567890') and 1234567890+1 == 1234567891)
-- silly loops
repeat until 1; repeat until true;
while false do end; while nil do end;
do -- test old bug (first name could not be an `upvalue')
local a; function f(x) x={a=1}; x={x=1}; x={G=1} end
end
function f (i)
if type(i) ~= 'number' then return i,'jojo'; end;
if i > 0 then return i, f(i-1); end;
end
x = {f(3), f(5), f(10);};
assert(x[1] == 3 and x[2] == 5 and x[3] == 10 and x[4] == 9 and x[12] == 1);
assert(x[nil] == nil)
x = {f'alo', f'xixi', nil};
assert(x[1] == 'alo' and x[2] == 'xixi' and x[3] == nil);
x = {f'alo'..'xixi'};
assert(x[1] == 'aloxixi')
x = {f{}}
assert(x[2] == 'jojo' and type(x[1]) == 'table')
local f = function (i)
if i < 10 then return 'a';
elseif i < 20 then return 'b';
elseif i < 30 then return 'c';
end;
end
assert(f(3) == 'a' and f(12) == 'b' and f(26) == 'c' and f(100) == nil)
for i=1,1000 do break; end;
n=100;
i=3;
t = {};
a=nil
while not a do
a=0; for i=1,n do for i=i,1,-1 do a=a+1; t[i]=1; end; end;
end
assert(a == n*(n+1)/2 and i==3);
assert(t[1] and t[n] and not t[0] and not t[n+1])
function f(b)
local x = 1;
repeat
local a;
if b==1 then local b=1; x=10; break
elseif b==2 then x=20; break;
elseif b==3 then x=30;
else local a,b,c,d=math.sin(1); x=x+1;
end
until x>=12;
return x;
end;
assert(f(1) == 10 and f(2) == 20 and f(3) == 30 and f(4)==12)
local f = function (i)
if i < 10 then return 'a'
elseif i < 20 then return 'b'
elseif i < 30 then return 'c'
else return 8
end
end
assert(f(3) == 'a' and f(12) == 'b' and f(26) == 'c' and f(100) == 8)
local a, b = nil, 23
x = {f(100)*2+3 or a, a or b+2}
assert(x[1] == 19 and x[2] == 25)
x = {f=2+3 or a, a = b+2}
assert(x.f == 5 and x.a == 25)
a={y=1}
x = {a.y}
assert(x[1] == 1)
function f(i)
while 1 do
if i>0 then i=i-1;
else return; end;
end;
end;
function g(i)
while 1 do
if i>0 then i=i-1
else return end
end
end
f(10); g(10);
do
function f () return 1,2,3; end
local a, b, c = f();
assert(a==1 and b==2 and c==3)
a, b, c = (f());
assert(a==1 and b==nil and c==nil)
end
local a,b = 3 and f();
assert(a==1 and b==nil)
function g() f(); return; end;
assert(g() == nil)
function g() return nil or f() end
a,b = g()
assert(a==1 and b==nil)
print'+';
f = [[
return function ( a , b , c , d , e )
local x = a >= b or c or ( d and e ) or nil
return x
end , { a = 1 , b = 2 >= 1 , } or { 1 };
]]
f = string.gsub(f, "%s+", "\n"); -- force a SETLINE between opcodes
f,a = load(f)();
assert(a.a == 1 and a.b)
function g (a,b,c,d,e)
if not (a>=b or c or d and e or nil) then return 0; else return 1; end;
end
function h (a,b,c,d,e)
while (a>=b or c or (d and e) or nil) do return 1; end;
return 0;
end;
assert(f(2,1) == true and g(2,1) == 1 and h(2,1) == 1)
assert(f(1,2,'a') == 'a' and g(1,2,'a') == 1 and h(1,2,'a') == 1)
assert(f(1,2,'a')
~= -- force SETLINE before nil
nil, "")
assert(f(1,2,'a') == 'a' and g(1,2,'a') == 1 and h(1,2,'a') == 1)
assert(f(1,2,nil,1,'x') == 'x' and g(1,2,nil,1,'x') == 1 and
h(1,2,nil,1,'x') == 1)
assert(f(1,2,nil,nil,'x') == nil and g(1,2,nil,nil,'x') == 0 and
h(1,2,nil,nil,'x') == 0)
assert(f(1,2,nil,1,nil) == nil and g(1,2,nil,1,nil) == 0 and
h(1,2,nil,1,nil) == 0)
assert(1 and 2<3 == true and 2<3 and 'a'<'b' == true)
x = 2<3 and not 3; assert(x==false)
x = 2<1 or (2>1 and 'a'); assert(x=='a')
do
local a; if nil then a=1; else a=2; end; -- this nil comes as PUSHNIL 2
assert(a==2)
end
function F(a)
assert(debug.getinfo(1, "n").name == 'F')
return a,2,3
end
a,b = F(1)~=nil; assert(a == true and b == nil);
a,b = F(nil)==nil; assert(a == true and b == nil)
----------------------------------------------------------------
------------------------------------------------------------------
-- sometimes will be 0, sometimes will not...
_ENV.GLOB1 = math.floor(os.time()) % 2
-- basic expressions with their respective values
local basiccases = {
{"nil", nil},
{"false", false},
{"true", true},
{"10", 10},
{"(0==_ENV.GLOB1)", 0 == _ENV.GLOB1},
}
print('testing short-circuit optimizations (' .. _ENV.GLOB1 .. ')')
-- operators with their respective values
local binops = {
{" and ", function (a,b) if not a then return a else return b end end},
{" or ", function (a,b) if a then return a else return b end end},
}
local cases = {}
-- creates all combinations of '(cases[i] op cases[n-i])' plus
-- 'not(cases[i] op cases[n-i])' (syntax + value)
local function createcases (n)
local res = {}
for i = 1, n - 1 do
for _, v1 in ipairs(cases[i]) do
for _, v2 in ipairs(cases[n - i]) do
for _, op in ipairs(binops) do
local t = {
"(" .. v1[1] .. op[1] .. v2[1] .. ")",
op[2](v1[2], v2[2])
}
res[#res + 1] = t
res[#res + 1] = {"not" .. t[1], not t[2]}
end
end
end
end
return res
end
-- do not do too many combinations for soft tests
local level = _soft and 3 or 4
cases[1] = basiccases
for i = 2, level do cases[i] = createcases(i) end
print("+")
local prog = [[if %s then IX = true end; return %s]]
local i = 0
for n = 1, level do
for _, v in pairs(cases[n]) do
local s = v[1]
local p = load(string.format(prog, s, s), "")
IX = false
assert(p() == v[2] and IX == not not v[2])
i = i + 1
if i % 60000 == 0 then print('+') end
end
end
------------------------------------------------------------------
-- testing some syntax errors (chosen through 'gcov')
checkload("for x do", "expected")
checkload("x:call", "expected")
if not _soft then
-- control structure too long
local s = string.rep("a = a + 1\n", 2^18)
s = "while true do " .. s .. "end"
checkload(s, "too long")
end
print'OK'

@ -0,0 +1,874 @@
-- $Id: coroutine.lua,v 1.42 2016/11/07 13:03:20 roberto Exp $
-- See Copyright Notice in file all.lua
print "testing coroutines"
local debug = require'debug'
local f
local main, ismain = coroutine.running()
assert(type(main) == "thread" and ismain)
assert(not coroutine.resume(main))
assert(not coroutine.isyieldable())
assert(not pcall(coroutine.yield))
-- trivial errors
assert(not pcall(coroutine.resume, 0))
assert(not pcall(coroutine.status, 0))
-- tests for multiple yield/resume arguments
local function eqtab (t1, t2)
assert(#t1 == #t2)
for i = 1, #t1 do
local v = t1[i]
assert(t2[i] == v)
end
end
_G.x = nil -- declare x
function foo (a, ...)
local x, y = coroutine.running()
assert(x == f and y == false)
-- next call should not corrupt coroutine (but must fail,
-- as it attempts to resume the running coroutine)
assert(coroutine.resume(f) == false)
assert(coroutine.status(f) == "running")
local arg = {...}
assert(coroutine.isyieldable())
for i=1,#arg do
_G.x = {coroutine.yield(table.unpack(arg[i]))}
end
return table.unpack(a)
end
f = coroutine.create(foo)
assert(type(f) == "thread" and coroutine.status(f) == "suspended")
assert(string.find(tostring(f), "thread"))
local s,a,b,c,d
s,a,b,c,d = coroutine.resume(f, {1,2,3}, {}, {1}, {'a', 'b', 'c'})
assert(s and a == nil and coroutine.status(f) == "suspended")
s,a,b,c,d = coroutine.resume(f)
eqtab(_G.x, {})
assert(s and a == 1 and b == nil)
s,a,b,c,d = coroutine.resume(f, 1, 2, 3)
eqtab(_G.x, {1, 2, 3})
assert(s and a == 'a' and b == 'b' and c == 'c' and d == nil)
s,a,b,c,d = coroutine.resume(f, "xuxu")
eqtab(_G.x, {"xuxu"})
assert(s and a == 1 and b == 2 and c == 3 and d == nil)
assert(coroutine.status(f) == "dead")
s, a = coroutine.resume(f, "xuxu")
assert(not s and string.find(a, "dead") and coroutine.status(f) == "dead")
-- yields in tail calls
local function foo (i) return coroutine.yield(i) end
f = coroutine.wrap(function ()
for i=1,10 do
assert(foo(i) == _G.x)
end
return 'a'
end)
for i=1,10 do _G.x = i; assert(f(i) == i) end
_G.x = 'xuxu'; assert(f('xuxu') == 'a')
-- recursive
function pf (n, i)
coroutine.yield(n)
pf(n*i, i+1)
end
f = coroutine.wrap(pf)
local s=1
for i=1,10 do
assert(f(1, 1) == s)
s = s*i
end
-- sieve
function gen (n)
return coroutine.wrap(function ()
for i=2,n do coroutine.yield(i) end
end)
end
function filter (p, g)
return coroutine.wrap(function ()
while 1 do
local n = g()
if n == nil then return end
if math.fmod(n, p) ~= 0 then coroutine.yield(n) end
end
end)
end
local x = gen(100)
local a = {}
while 1 do
local n = x()
if n == nil then break end
table.insert(a, n)
x = filter(n, x)
end
assert(#a == 25 and a[#a] == 97)
x, a = nil
-- yielding across C boundaries
co = coroutine.wrap(function()
assert(not pcall(table.sort,{1,2,3}, coroutine.yield))
assert(coroutine.isyieldable())
coroutine.yield(20)
return 30
end)
assert(co() == 20)
assert(co() == 30)
local f = function (s, i) return coroutine.yield(i) end
local f1 = coroutine.wrap(function ()
return xpcall(pcall, function (...) return ... end,
function ()
local s = 0
for i in f, nil, 1 do pcall(function () s = s + i end) end
error({s})
end)
end)
f1()
for i = 1, 10 do assert(f1(i) == i) end
local r1, r2, v = f1(nil)
assert(r1 and not r2 and v[1] == (10 + 1)*10/2)
function f (a, b) a = coroutine.yield(a); error{a + b} end
function g(x) return x[1]*2 end
co = coroutine.wrap(function ()
coroutine.yield(xpcall(f, g, 10, 20))
end)
assert(co() == 10)
r, msg = co(100)
assert(not r and msg == 240)
-- unyieldable C call
do
local function f (c)
assert(not coroutine.isyieldable())
return c .. c
end
local co = coroutine.wrap(function (c)
assert(coroutine.isyieldable())
local s = string.gsub("a", ".", f)
return s
end)
assert(co() == "aa")
end
-- errors in coroutines
function foo ()
assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1)
assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined)
coroutine.yield(3)
error(foo)
end
function goo() foo() end
x = coroutine.wrap(goo)
assert(x() == 3)
local a,b = pcall(x)
assert(not a and b == foo)
x = coroutine.create(goo)
a,b = coroutine.resume(x)
assert(a and b == 3)
a,b = coroutine.resume(x)
assert(not a and b == foo and coroutine.status(x) == "dead")
a,b = coroutine.resume(x)
assert(not a and string.find(b, "dead") and coroutine.status(x) == "dead")
-- co-routines x for loop
function all (a, n, k)
if k == 0 then coroutine.yield(a)
else
for i=1,n do
a[k] = i
all(a, n, k-1)
end
end
end
local a = 0
for t in coroutine.wrap(function () all({}, 5, 4) end) do
a = a+1
end
assert(a == 5^4)
-- access to locals of collected corroutines
local C = {}; setmetatable(C, {__mode = "kv"})
local x = coroutine.wrap (function ()
local a = 10
local function f () a = a+10; return a end
while true do
a = a+1
coroutine.yield(f)
end
end)
C[1] = x;
local f = x()
assert(f() == 21 and x()() == 32 and x() == f)
x = nil
collectgarbage()
assert(C[1] == nil)
assert(f() == 43 and f() == 53)
-- old bug: attempt to resume itself
function co_func (current_co)
assert(coroutine.running() == current_co)
assert(coroutine.resume(current_co) == false)
coroutine.yield(10, 20)
assert(coroutine.resume(current_co) == false)
coroutine.yield(23)
return 10
end
local co = coroutine.create(co_func)
local a,b,c = coroutine.resume(co, co)
assert(a == true and b == 10 and c == 20)
a,b = coroutine.resume(co, co)
assert(a == true and b == 23)
a,b = coroutine.resume(co, co)
assert(a == true and b == 10)
assert(coroutine.resume(co, co) == false)
assert(coroutine.resume(co, co) == false)
-- other old bug when attempting to resume itself
-- (trigger C-code assertions)
do
local A = coroutine.running()
local B = coroutine.create(function() return coroutine.resume(A) end)
local st, res = coroutine.resume(B)
assert(st == true and res == false)
A = coroutine.wrap(function() return pcall(A, 1) end)
st, res = A()
assert(not st and string.find(res, "non%-suspended"))
end
-- attempt to resume 'normal' coroutine
local co1, co2
co1 = coroutine.create(function () return co2() end)
co2 = coroutine.wrap(function ()
assert(coroutine.status(co1) == 'normal')
assert(not coroutine.resume(co1))
coroutine.yield(3)
end)
a,b = coroutine.resume(co1)
assert(a and b == 3)
assert(coroutine.status(co1) == 'dead')
-- infinite recursion of coroutines
a = function(a) coroutine.wrap(a)(a) end
assert(not pcall(a, a))
a = nil
-- access to locals of erroneous coroutines
local x = coroutine.create (function ()
local a = 10
_G.f = function () a=a+1; return a end
error('x')
end)
assert(not coroutine.resume(x))
-- overwrite previous position of local `a'
assert(not coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1))
assert(_G.f() == 11)
assert(_G.f() == 12)
if not T then
(Message or print)('\n >>> testC not active: skipping yield/hook tests <<<\n')
else
print "testing yields inside hooks"
local turn
function fact (t, x)
assert(turn == t)
if x == 0 then return 1
else return x*fact(t, x-1)
end
end
local A, B = 0, 0
local x = coroutine.create(function ()
T.sethook("yield 0", "", 2)
A = fact("A", 6)
end)
local y = coroutine.create(function ()
T.sethook("yield 0", "", 3)
B = fact("B", 7)
end)
while A==0 or B==0 do -- A ~= 0 when 'x' finishes (similar for 'B','y')
if A==0 then turn = "A"; assert(T.resume(x)) end
if B==0 then turn = "B"; assert(T.resume(y)) end
end
assert(B // A == 7) -- fact(7) // fact(6)
local line = debug.getinfo(1, "l").currentline + 2 -- get line number
local function foo ()
local x = 10 --<< this line is 'line'
x = x + 10
_G.XX = x
end
-- testing yields in line hook
local co = coroutine.wrap(function ()
T.sethook("setglobal X; yield 0", "l", 0); foo(); return 10 end)
_G.XX = nil;
_G.X = nil; co(); assert(_G.X == line)
_G.X = nil; co(); assert(_G.X == line + 1)
_G.X = nil; co(); assert(_G.X == line + 2 and _G.XX == nil)
_G.X = nil; co(); assert(_G.X == line + 3 and _G.XX == 20)
assert(co() == 10)
-- testing yields in count hook
co = coroutine.wrap(function ()
T.sethook("yield 0", "", 1); foo(); return 10 end)
_G.XX = nil;
local c = 0
repeat c = c + 1; local a = co() until a == 10
assert(_G.XX == 20 and c >= 5)
co = coroutine.wrap(function ()
T.sethook("yield 0", "", 2); foo(); return 10 end)
_G.XX = nil;
local c = 0
repeat c = c + 1; local a = co() until a == 10
assert(_G.XX == 20 and c >= 5)
_G.X = nil; _G.XX = nil
do
-- testing debug library on a coroutine suspended inside a hook
-- (bug in 5.2/5.3)
c = coroutine.create(function (a, ...)
T.sethook("yield 0", "l") -- will yield on next two lines
assert(a == 10)
return ...
end)
assert(coroutine.resume(c, 1, 2, 3)) -- start coroutine
local n,v = debug.getlocal(c, 0, 1) -- check its local
assert(n == "a" and v == 1)
n,v = debug.getlocal(c, 0, -1) -- check varargs
assert(v == 2)
n,v = debug.getlocal(c, 0, -2)
assert(v == 3)
assert(debug.setlocal(c, 0, 1, 10)) -- test 'setlocal'
assert(debug.setlocal(c, 0, -2, 20))
local t = debug.getinfo(c, 0) -- test 'getinfo'
assert(t.currentline == t.linedefined + 1)
assert(not debug.getinfo(c, 1)) -- no other level
assert(coroutine.resume(c)) -- run next line
v = {coroutine.resume(c)} -- finish coroutine
assert(v[1] == true and v[2] == 2 and v[3] == 20 and v[4] == nil)
assert(not coroutine.resume(c))
end
do
-- testing debug library on last function in a suspended coroutine
-- (bug in 5.2/5.3)
local c = coroutine.create(function () T.testC("yield 1", 10, 20) end)
local a, b = coroutine.resume(c)
assert(a and b == 20)
assert(debug.getinfo(c, 0).linedefined == -1)
a, b = debug.getlocal(c, 0, 2)
assert(b == 10)
end
print "testing coroutine API"
-- reusing a thread
assert(T.testC([[
newthread # create thread
pushvalue 2 # push body
pushstring 'a a a' # push argument
xmove 0 3 2 # move values to new thread
resume -1, 1 # call it first time
pushstatus
xmove 3 0 0 # move results back to stack
setglobal X # result
setglobal Y # status
pushvalue 2 # push body (to call it again)
pushstring 'b b b'
xmove 0 3 2
resume -1, 1 # call it again
pushstatus
xmove 3 0 0
return 1 # return result
]], function (...) return ... end) == 'b b b')
assert(X == 'a a a' and Y == 'OK')
-- resuming running coroutine
C = coroutine.create(function ()
return T.testC([[
pushnum 10;
pushnum 20;
resume -3 2;
pushstatus
gettop;
return 3]], C)
end)
local a, b, c, d = coroutine.resume(C)
assert(a == true and string.find(b, "non%-suspended") and
c == "ERRRUN" and d == 4)
a, b, c, d = T.testC([[
rawgeti R 1 # get main thread
pushnum 10;
pushnum 20;
resume -3 2;
pushstatus
gettop;
return 4]])
assert(a == coroutine.running() and string.find(b, "non%-suspended") and
c == "ERRRUN" and d == 4)
-- using a main thread as a coroutine
local state = T.newstate()
T.loadlib(state)
assert(T.doremote(state, [[
coroutine = require'coroutine';
X = function (x) coroutine.yield(x, 'BB'); return 'CC' end;
return 'ok']]))
t = table.pack(T.testC(state, [[
rawgeti R 1 # get main thread
pushstring 'XX'
getglobal X # get function for body
pushstring AA # arg
resume 1 1 # 'resume' shadows previous stack!
gettop
setglobal T # top
setglobal B # second yielded value
setglobal A # fist yielded value
rawgeti R 1 # get main thread
pushnum 5 # arg (noise)
resume 1 1 # after coroutine ends, previous stack is back
pushstatus
return *
]]))
assert(t.n == 4 and t[2] == 'XX' and t[3] == 'CC' and t[4] == 'OK')
assert(T.doremote(state, "return T") == '2')
assert(T.doremote(state, "return A") == 'AA')
assert(T.doremote(state, "return B") == 'BB')
T.closestate(state)
print'+'
end
-- leaving a pending coroutine open
_X = coroutine.wrap(function ()
local a = 10
local x = function () a = a+1 end
coroutine.yield()
end)
_X()
if not _soft then
-- bug (stack overflow)
local j = 2^9
local lim = 1000000 -- (C stack limit; assume 32-bit machine)
local t = {lim - 10, lim - 5, lim - 1, lim, lim + 1}
for i = 1, #t do
local j = t[i]
co = coroutine.create(function()
local t = {}
for i = 1, j do t[i] = i end
return table.unpack(t)
end)
local r, msg = coroutine.resume(co)
assert(not r)
end
co = nil
end
assert(coroutine.running() == main)
print"+"
print"testing yields inside metamethods"
local mt = {
__eq = function(a,b) coroutine.yield(nil, "eq"); return a.x == b.x end,
__lt = function(a,b) coroutine.yield(nil, "lt"); return a.x < b.x end,
__le = function(a,b) coroutine.yield(nil, "le"); return a - b <= 0 end,
__add = function(a,b) coroutine.yield(nil, "add"); return a.x + b.x end,
__sub = function(a,b) coroutine.yield(nil, "sub"); return a.x - b.x end,
__mod = function(a,b) coroutine.yield(nil, "mod"); return a.x % b.x end,
__unm = function(a,b) coroutine.yield(nil, "unm"); return -a.x end,
__bnot = function(a,b) coroutine.yield(nil, "bnot"); return ~a.x end,
__shl = function(a,b) coroutine.yield(nil, "shl"); return a.x << b.x end,
__shr = function(a,b) coroutine.yield(nil, "shr"); return a.x >> b.x end,
__band = function(a,b)
a = type(a) == "table" and a.x or a
b = type(b) == "table" and b.x or b
coroutine.yield(nil, "band")
return a & b
end,
__bor = function(a,b) coroutine.yield(nil, "bor"); return a.x | b.x end,
__bxor = function(a,b) coroutine.yield(nil, "bxor"); return a.x ~ b.x end,
__concat = function(a,b)
coroutine.yield(nil, "concat");
a = type(a) == "table" and a.x or a
b = type(b) == "table" and b.x or b
return a .. b
end,
__index = function (t,k) coroutine.yield(nil, "idx"); return t.k[k] end,
__newindex = function (t,k,v) coroutine.yield(nil, "nidx"); t.k[k] = v end,
}
local function new (x)
return setmetatable({x = x, k = {}}, mt)
end
local a = new(10)
local b = new(12)
local c = new"hello"
local function run (f, t)
local i = 1
local c = coroutine.wrap(f)
while true do
local res, stat = c()
if res then assert(t[i] == nil); return res, t end
assert(stat == t[i])
i = i + 1
end
end
assert(run(function () if (a>=b) then return '>=' else return '<' end end,
{"le", "sub"}) == "<")
-- '<=' using '<'
mt.__le = nil
assert(run(function () if (a<=b) then return '<=' else return '>' end end,
{"lt"}) == "<=")
assert(run(function () if (a==b) then return '==' else return '~=' end end,
{"eq"}) == "~=")
assert(run(function () return a & b + a end, {"add", "band"}) == 2)
assert(run(function () return a % b end, {"mod"}) == 10)
assert(run(function () return ~a & b end, {"bnot", "band"}) == ~10 & 12)
assert(run(function () return a | b end, {"bor"}) == 10 | 12)
assert(run(function () return a ~ b end, {"bxor"}) == 10 ~ 12)
assert(run(function () return a << b end, {"shl"}) == 10 << 12)
assert(run(function () return a >> b end, {"shr"}) == 10 >> 12)
assert(run(function () return a..b end, {"concat"}) == "1012")
assert(run(function() return a .. b .. c .. a end,
{"concat", "concat", "concat"}) == "1012hello10")
assert(run(function() return "a" .. "b" .. a .. "c" .. c .. b .. "x" end,
{"concat", "concat", "concat"}) == "ab10chello12x")
do -- a few more tests for comparsion operators
local mt1 = {
__le = function (a,b)
coroutine.yield(10)
return
(type(a) == "table" and a.x or a) <= (type(b) == "table" and b.x or b)
end,
__lt = function (a,b)
coroutine.yield(10)
return
(type(a) == "table" and a.x or a) < (type(b) == "table" and b.x or b)
end,
}
local mt2 = { __lt = mt1.__lt } -- no __le
local function run (f)
local co = coroutine.wrap(f)
local res
repeat
res = co()
until res ~= 10
return res
end
local function test ()
local a1 = setmetatable({x=1}, mt1)
local a2 = setmetatable({x=2}, mt2)
assert(a1 < a2)
assert(a1 <= a2)
assert(1 < a2)
assert(1 <= a2)
assert(2 > a1)
assert(2 >= a2)
return true
end
run(test)
end
assert(run(function ()
a.BB = print
return a.BB
end, {"nidx", "idx"}) == print)
-- getuptable & setuptable
do local _ENV = _ENV
f = function () AAA = BBB + 1; return AAA end
end
g = new(10); g.k.BBB = 10;
debug.setupvalue(f, 1, g)
assert(run(f, {"idx", "nidx", "idx"}) == 11)
assert(g.k.AAA == 11)
print"+"
print"testing yields inside 'for' iterators"
local f = function (s, i)
if i%2 == 0 then coroutine.yield(nil, "for") end
if i < s then return i + 1 end
end
assert(run(function ()
local s = 0
for i in f, 4, 0 do s = s + i end
return s
end, {"for", "for", "for"}) == 10)
-- tests for coroutine API
if T==nil then
(Message or print)('\n >>> testC not active: skipping coroutine API tests <<<\n')
return
end
print('testing coroutine API')
local function apico (...)
local x = {...}
return coroutine.wrap(function ()
return T.testC(table.unpack(x))
end)
end
local a = {apico(
[[
pushstring errorcode
pcallk 1 0 2;
invalid command (should not arrive here)
]],
[[return *]],
"stackmark",
error
)()}
assert(#a == 4 and
a[3] == "stackmark" and
a[4] == "errorcode" and
_G.status == "ERRRUN" and
_G.ctx == 2) -- 'ctx' to pcallk
local co = apico(
"pushvalue 2; pushnum 10; pcallk 1 2 3; invalid command;",
coroutine.yield,
"getglobal status; getglobal ctx; pushvalue 2; pushstring a; pcallk 1 0 4; invalid command",
"getglobal status; getglobal ctx; return *")
assert(co() == 10)
assert(co(20, 30) == 'a')
a = {co()}
assert(#a == 10 and
a[2] == coroutine.yield and
a[5] == 20 and a[6] == 30 and
a[7] == "YIELD" and a[8] == 3 and
a[9] == "YIELD" and a[10] == 4)
assert(not pcall(co)) -- coroutine is dead now
f = T.makeCfunc("pushnum 3; pushnum 5; yield 1;")
co = coroutine.wrap(function ()
assert(f() == 23); assert(f() == 23); return 10
end)
assert(co(23,16) == 5)
assert(co(23,16) == 5)
assert(co(23,16) == 10)
-- testing coroutines with C bodies
f = T.makeCfunc([[
pushnum 102
yieldk 1 U2
cannot be here!
]],
[[ # continuation
pushvalue U3 # accessing upvalues inside a continuation
pushvalue U4
return *
]], 23, "huu")
x = coroutine.wrap(f)
assert(x() == 102)
eqtab({x()}, {23, "huu"})
f = T.makeCfunc[[pushstring 'a'; pushnum 102; yield 2; ]]
a, b, c, d = T.testC([[newthread; pushvalue 2; xmove 0 3 1; resume 3 0;
pushstatus; xmove 3 0 0; resume 3 0; pushstatus;
return 4; ]], f)
assert(a == 'YIELD' and b == 'a' and c == 102 and d == 'OK')
-- testing chain of suspendable C calls
local count = 3 -- number of levels
f = T.makeCfunc([[
remove 1; # remove argument
pushvalue U3; # get selection function
call 0 1; # call it (result is 'f' or 'yield')
pushstring hello # single argument for selected function
pushupvalueindex 2; # index of continuation program
callk 1 -1 .; # call selected function
errorerror # should never arrive here
]],
[[
# continuation program
pushnum 34 # return value
return * # return all results
]],
function () -- selection function
count = count - 1
if count == 0 then return coroutine.yield
else return f
end
end
)
co = coroutine.wrap(function () return f(nil) end)
assert(co() == "hello") -- argument to 'yield'
a = {co()}
-- three '34's (one from each pending C call)
assert(#a == 3 and a[1] == a[2] and a[2] == a[3] and a[3] == 34)
-- testing yields with continuations
co = coroutine.wrap(function (...) return
T.testC([[ # initial function
yieldk 1 2
cannot be here!
]],
[[ # 1st continuation
yieldk 0 3
cannot be here!
]],
[[ # 2nd continuation
yieldk 0 4
cannot be here!
]],
[[ # 3th continuation
pushvalue 6 # function which is last arg. to 'testC' here
pushnum 10; pushnum 20;
pcall 2 0 0 # call should throw an error and return to next line
pop 1 # remove error message
pushvalue 6
getglobal status; getglobal ctx
pcallk 2 2 5 # call should throw an error and jump to continuation
cannot be here!
]],
[[ # 4th (and last) continuation
return *
]],
-- function called by 3th continuation
function (a,b) x=a; y=b; error("errmsg") end,
...
)
end)
local a = {co(3,4,6)}
assert(a[1] == 6 and a[2] == nil)
a = {co()}; assert(a[1] == nil and _G.status == "YIELD" and _G.ctx == 2)
a = {co()}; assert(a[1] == nil and _G.status == "YIELD" and _G.ctx == 3)
a = {co(7,8)};
-- original arguments
assert(type(a[1]) == 'string' and type(a[2]) == 'string' and
type(a[3]) == 'string' and type(a[4]) == 'string' and
type(a[5]) == 'string' and type(a[6]) == 'function')
-- arguments left from fist resume
assert(a[7] == 3 and a[8] == 4)
-- arguments to last resume
assert(a[9] == 7 and a[10] == 8)
-- error message and nothing more
assert(a[11]:find("errmsg") and #a == 11)
-- check arguments to pcallk
assert(x == "YIELD" and y == 4)
assert(not pcall(co)) -- coroutine should be dead
-- bug in nCcalls
local co = coroutine.wrap(function ()
local a = {pcall(pcall,pcall,pcall,pcall,pcall,pcall,pcall,error,"hi")}
return pcall(assert, table.unpack(a))
end)
local a = {co()}
assert(a[10] == "hi")
print'OK'

@ -0,0 +1,857 @@
-- $Id: db.lua,v 1.79 2016/11/07 13:02:34 roberto Exp $
-- See Copyright Notice in file all.lua
-- testing debug library
local debug = require "debug"
local function dostring(s) return assert(load(s))() end
print"testing debug library and debug information"
do
local a=1
end
assert(not debug.gethook())
local testline = 19 -- line where 'test' is defined
function test (s, l, p) -- this must be line 19
collectgarbage() -- avoid gc during trace
local function f (event, line)
assert(event == 'line')
local l = table.remove(l, 1)
if p then print(l, line) end
assert(l == line, "wrong trace!!")
end
debug.sethook(f,"l"); load(s)(); debug.sethook()
assert(#l == 0)
end
do
assert(not pcall(debug.getinfo, print, "X")) -- invalid option
assert(not debug.getinfo(1000)) -- out of range level
assert(not debug.getinfo(-1)) -- out of range level
local a = debug.getinfo(print)
assert(a.what == "C" and a.short_src == "[C]")
a = debug.getinfo(print, "L")
assert(a.activelines == nil)
local b = debug.getinfo(test, "SfL")
assert(b.name == nil and b.what == "Lua" and b.linedefined == testline and
b.lastlinedefined == b.linedefined + 10 and
b.func == test and not string.find(b.short_src, "%["))
assert(b.activelines[b.linedefined + 1] and
b.activelines[b.lastlinedefined])
assert(not b.activelines[b.linedefined] and
not b.activelines[b.lastlinedefined + 1])
end
-- test file and string names truncation
a = "function f () end"
local function dostring (s, x) return load(s, x)() end
dostring(a)
assert(debug.getinfo(f).short_src == string.format('[string "%s"]', a))
dostring(a..string.format("; %s\n=1", string.rep('p', 400)))
assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
dostring(a..string.format("; %s=1", string.rep('p', 400)))
assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
dostring("\n"..a)
assert(debug.getinfo(f).short_src == '[string "..."]')
dostring(a, "")
assert(debug.getinfo(f).short_src == '[string ""]')
dostring(a, "@xuxu")
assert(debug.getinfo(f).short_src == "xuxu")
dostring(a, "@"..string.rep('p', 1000)..'t')
assert(string.find(debug.getinfo(f).short_src, "^%.%.%.p*t$"))
dostring(a, "=xuxu")
assert(debug.getinfo(f).short_src == "xuxu")
dostring(a, string.format("=%s", string.rep('x', 500)))
assert(string.find(debug.getinfo(f).short_src, "^x*$"))
dostring(a, "=")
assert(debug.getinfo(f).short_src == "")
a = nil; f = nil;
repeat
local g = {x = function ()
local a = debug.getinfo(2)
assert(a.name == 'f' and a.namewhat == 'local')
a = debug.getinfo(1)
assert(a.name == 'x' and a.namewhat == 'field')
return 'xixi'
end}
local f = function () return 1+1 and (not 1 or g.x()) end
assert(f() == 'xixi')
g = debug.getinfo(f)
assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name)
function f (x, name) -- local!
name = name or 'f'
local a = debug.getinfo(1)
assert(a.name == name and a.namewhat == 'local')
return x
end
-- breaks in different conditions
if 3>4 then break end; f()
if 3<4 then a=1 else break end; f()
while 1 do local x=10; break end; f()
local b = 1
if 3>4 then return math.sin(1) end; f()
a = 3<4; f()
a = 3<4 or 1; f()
repeat local x=20; if 4>3 then f() else break end; f() until 1
g = {}
f(g).x = f(2) and f(10)+f(9)
assert(g.x == f(19))
function g(x) if not x then return 3 end return (x('a', 'x')) end
assert(g(f) == 'a')
until 1
test([[if
math.sin(1)
then
a=1
else
a=2
end
]], {2,3,4,7})
test([[--
if nil then
a=1
else
a=2
end
]], {2,5,6})
test([[a=1
repeat
a=a+1
until a==3
]], {1,3,4,3,4})
test([[ do
return
end
]], {2})
test([[local a
a=1
while a<=3 do
a=a+1
end
]], {1,2,3,4,3,4,3,4,3,5})
test([[while math.sin(1) do
if math.sin(1)
then break
end
end
a=1]], {1,2,3,6})
test([[for i=1,3 do
a=i
end
]], {1,2,1,2,1,2,1,3})
test([[for i,v in pairs{'a','b'} do
a=tostring(i) .. v
end
]], {1,2,1,2,1,3})
test([[for i=1,4 do a=1 end]], {1,1,1,1,1})
print'+'
-- invalid levels in [gs]etlocal
assert(not pcall(debug.getlocal, 20, 1))
assert(not pcall(debug.setlocal, -1, 1, 10))
-- parameter names
local function foo (a,b,...) local d, e end
local co = coroutine.create(foo)
assert(debug.getlocal(foo, 1) == 'a')
assert(debug.getlocal(foo, 2) == 'b')
assert(not debug.getlocal(foo, 3))
assert(debug.getlocal(co, foo, 1) == 'a')
assert(debug.getlocal(co, foo, 2) == 'b')
assert(not debug.getlocal(co, foo, 3))
assert(not debug.getlocal(print, 1))
-- varargs
local function foo (a, ...)
local t = table.pack(...)
for i = 1, t.n do
local n, v = debug.getlocal(1, -i)
assert(n == "(*vararg)" and v == t[i])
end
assert(not debug.getlocal(1, -(t.n + 1)))
assert(not debug.setlocal(1, -(t.n + 1), 30))
if t.n > 0 then
(function (x)
assert(debug.setlocal(2, -1, x) == "(*vararg)")
assert(debug.setlocal(2, -t.n, x) == "(*vararg)")
end)(430)
assert(... == 430)
end
end
foo()
foo(print)
foo(200, 3, 4)
local a = {}
for i = 1, (_soft and 100 or 1000) do a[i] = i end
foo(table.unpack(a))
a = nil
-- access to vararg in non-vararg function
local function foo () return debug.getlocal(1, -1) end
assert(not foo(10))
do -- test hook presence in debug info
assert(not debug.gethook())
local count = 0
local function f ()
assert(debug.getinfo(1).namewhat == "hook")
local sndline = string.match(debug.traceback(), "\n(.-)\n")
assert(string.find(sndline, "hook"))
count = count + 1
end
debug.sethook(f, "l")
local a = 0
_ENV.a = a
a = 1
debug.sethook()
assert(count == 4)
end
a = {}; L = nil
local glob = 1
local oldglob = glob
debug.sethook(function (e,l)
collectgarbage() -- force GC during a hook
local f, m, c = debug.gethook()
assert(m == 'crl' and c == 0)
if e == "line" then
if glob ~= oldglob then
L = l-1 -- get the first line where "glob" has changed
oldglob = glob
end
elseif e == "call" then
local f = debug.getinfo(2, "f").func
a[f] = 1
else assert(e == "return")
end
end, "crl")
function f(a,b)
collectgarbage()
local _, x = debug.getlocal(1, 1)
local _, y = debug.getlocal(1, 2)
assert(x == a and y == b)
assert(debug.setlocal(2, 3, "pera") == "AA".."AA")
assert(debug.setlocal(2, 4, "maçã") == "B")
x = debug.getinfo(2)
assert(x.func == g and x.what == "Lua" and x.name == 'g' and
x.nups == 2 and string.find(x.source, "^@.*db%.lua$"))
glob = glob+1
assert(debug.getinfo(1, "l").currentline == L+1)
assert(debug.getinfo(1, "l").currentline == L+2)
end
function foo()
glob = glob+1
assert(debug.getinfo(1, "l").currentline == L+1)
end; foo() -- set L
-- check line counting inside strings and empty lines
_ = 'alo\
alo' .. [[
]]
--[[
]]
assert(debug.getinfo(1, "l").currentline == L+11) -- check count of lines
function g(...)
local arg = {...}
do local a,b,c; a=math.sin(40); end
local feijao
local AAAA,B = "xuxu", "mamão"
f(AAAA,B)
assert(AAAA == "pera" and B == "maçã")
do
local B = 13
local x,y = debug.getlocal(1,5)
assert(x == 'B' and y == 13)
end
end
g()
assert(a[f] and a[g] and a[assert] and a[debug.getlocal] and not a[print])
-- tests for manipulating non-registered locals (C and Lua temporaries)
local n, v = debug.getlocal(0, 1)
assert(v == 0 and n == "(*temporary)")
local n, v = debug.getlocal(0, 2)
assert(v == 2 and n == "(*temporary)")
assert(not debug.getlocal(0, 3))
assert(not debug.getlocal(0, 0))
function f()
assert(select(2, debug.getlocal(2,3)) == 1)
assert(not debug.getlocal(2,4))
debug.setlocal(2, 3, 10)
return 20
end
function g(a,b) return (a+1) + f() end
assert(g(0,0) == 30)
debug.sethook(nil);
assert(debug.gethook() == nil)
-- testing access to function arguments
local function collectlocals (level)
local tab = {}
for i = 1, math.huge do
local n, v = debug.getlocal(level + 1, i)
if not (n and string.find(n, "^[a-zA-Z0-9_]+$")) then
break -- consider only real variables
end
tab[n] = v
end
return tab
end
X = nil
a = {}
function a:f (a, b, ...) local arg = {...}; local c = 13 end
debug.sethook(function (e)
assert(e == "call")
dostring("XX = 12") -- test dostring inside hooks
-- testing errors inside hooks
assert(not pcall(load("a='joao'+1")))
debug.sethook(function (e, l)
assert(debug.getinfo(2, "l").currentline == l)
local f,m,c = debug.gethook()
assert(e == "line")
assert(m == 'l' and c == 0)
debug.sethook(nil) -- hook is called only once
assert(not X) -- check that
X = collectlocals(2)
end, "l")
end, "c")
a:f(1,2,3,4,5)
assert(X.self == a and X.a == 1 and X.b == 2 and X.c == nil)
assert(XX == 12)
assert(debug.gethook() == nil)
-- testing access to local variables in return hook (bug in 5.2)
do
local function foo (a, b)
do local x,y,z end
local c, d = 10, 20
return
end
local function aux ()
if debug.getinfo(2).name == "foo" then
foo = nil -- to signal that it found 'foo'
local tab = {a = 100, b = 200, c = 10, d = 20}
for n, v in pairs(collectlocals(2)) do
assert(tab[n] == v)
tab[n] = nil
end
assert(next(tab) == nil) -- 'tab' must be empty
end
end
debug.sethook(aux, "r"); foo(100, 200); debug.sethook()
assert(foo == nil)
end
-- testing upvalue access
local function getupvalues (f)
local t = {}
local i = 1
while true do
local name, value = debug.getupvalue(f, i)
if not name then break end
assert(not t[name])
t[name] = value
i = i + 1
end
return t
end
local a,b,c = 1,2,3
local function foo1 (a) b = a; return c end
local function foo2 (x) a = x; return c+b end
assert(not debug.getupvalue(foo1, 3))
assert(not debug.getupvalue(foo1, 0))
assert(not debug.setupvalue(foo1, 3, "xuxu"))
local t = getupvalues(foo1)
assert(t.a == nil and t.b == 2 and t.c == 3)
t = getupvalues(foo2)
assert(t.a == 1 and t.b == 2 and t.c == 3)
assert(debug.setupvalue(foo1, 1, "xuxu") == "b")
assert(({debug.getupvalue(foo2, 3)})[2] == "xuxu")
-- upvalues of C functions are allways "called" "" (the empty string)
assert(debug.getupvalue(string.gmatch("x", "x"), 1) == "")
-- testing count hooks
local a=0
debug.sethook(function (e) a=a+1 end, "", 1)
a=0; for i=1,1000 do end; assert(1000 < a and a < 1012)
debug.sethook(function (e) a=a+1 end, "", 4)
a=0; for i=1,1000 do end; assert(250 < a and a < 255)
local f,m,c = debug.gethook()
assert(m == "" and c == 4)
debug.sethook(function (e) a=a+1 end, "", 4000)
a=0; for i=1,1000 do end; assert(a == 0)
do
debug.sethook(print, "", 2^24 - 1) -- count upperbound
local f,m,c = debug.gethook()
assert(({debug.gethook()})[3] == 2^24 - 1)
end
debug.sethook()
-- tests for tail calls
local function f (x)
if x then
assert(debug.getinfo(1, "S").what == "Lua")
assert(debug.getinfo(1, "t").istailcall == true)
local tail = debug.getinfo(2)
assert(tail.func == g1 and tail.istailcall == true)
assert(debug.getinfo(3, "S").what == "main")
print"+"
end
end
function g(x) return f(x) end
function g1(x) g(x) end
local function h (x) local f=g1; return f(x) end
h(true)
local b = {}
debug.sethook(function (e) table.insert(b, e) end, "cr")
h(false)
debug.sethook()
local res = {"return", -- first return (from sethook)
"call", "tail call", "call", "tail call",
"return", "return",
"call", -- last call (to sethook)
}
for i = 1, #res do assert(res[i] == table.remove(b, 1)) end
b = 0
debug.sethook(function (e)
if e == "tail call" then
b = b + 1
assert(debug.getinfo(2, "t").istailcall == true)
else
assert(debug.getinfo(2, "t").istailcall == false)
end
end, "c")
h(false)
debug.sethook()
assert(b == 2) -- two tail calls
lim = _soft and 3000 or 30000
local function foo (x)
if x==0 then
assert(debug.getinfo(2).what == "main")
local info = debug.getinfo(1)
assert(info.istailcall == true and info.func == foo)
else return foo(x-1)
end
end
foo(lim)
print"+"
-- testing local function information
co = load[[
local A = function ()
return x
end
return
]]
local a = 0
-- 'A' should be visible to debugger only after its complete definition
debug.sethook(function (e, l)
if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == "(*temporary)")
elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A")
end
end, "l")
co() -- run local function definition
debug.sethook() -- turn off hook
assert(a == 2) -- ensure all two lines where hooked
-- testing traceback
assert(debug.traceback(print) == print)
assert(debug.traceback(print, 4) == print)
assert(string.find(debug.traceback("hi", 4), "^hi\n"))
assert(string.find(debug.traceback("hi"), "^hi\n"))
assert(not string.find(debug.traceback("hi"), "'debug.traceback'"))
assert(string.find(debug.traceback("hi", 0), "'debug.traceback'"))
assert(string.find(debug.traceback(), "^stack traceback:\n"))
do -- C-function names in traceback
local st, msg = (function () return pcall end)()(debug.traceback)
assert(st == true and string.find(msg, "pcall"))
end
-- testing nparams, nups e isvararg
local t = debug.getinfo(print, "u")
assert(t.isvararg == true and t.nparams == 0 and t.nups == 0)
t = debug.getinfo(function (a,b,c) end, "u")
assert(t.isvararg == false and t.nparams == 3 and t.nups == 0)
t = debug.getinfo(function (a,b,...) return t[a] end, "u")
assert(t.isvararg == true and t.nparams == 2 and t.nups == 1)
t = debug.getinfo(1) -- main
assert(t.isvararg == true and t.nparams == 0 and t.nups == 1 and
debug.getupvalue(t.func, 1) == "_ENV")
-- testing debugging of coroutines
local function checktraceback (co, p, level)
local tb = debug.traceback(co, nil, level)
local i = 0
for l in string.gmatch(tb, "[^\n]+\n?") do
assert(i == 0 or string.find(l, p[i]))
i = i+1
end
assert(p[i] == nil)
end
local function f (n)
if n > 0 then f(n-1)
else coroutine.yield() end
end
local co = coroutine.create(f)
coroutine.resume(co, 3)
checktraceback(co, {"yield", "db.lua", "db.lua", "db.lua", "db.lua"})
checktraceback(co, {"db.lua", "db.lua", "db.lua", "db.lua"}, 1)
checktraceback(co, {"db.lua", "db.lua", "db.lua"}, 2)
checktraceback(co, {"db.lua"}, 4)
checktraceback(co, {}, 40)
co = coroutine.create(function (x)
local a = 1
coroutine.yield(debug.getinfo(1, "l"))
coroutine.yield(debug.getinfo(1, "l").currentline)
return a
end)
local tr = {}
local foo = function (e, l) if l then table.insert(tr, l) end end
debug.sethook(co, foo, "lcr")
local _, l = coroutine.resume(co, 10)
local x = debug.getinfo(co, 1, "lfLS")
assert(x.currentline == l.currentline and x.activelines[x.currentline])
assert(type(x.func) == "function")
for i=x.linedefined + 1, x.lastlinedefined do
assert(x.activelines[i])
x.activelines[i] = nil
end
assert(next(x.activelines) == nil) -- no 'extra' elements
assert(not debug.getinfo(co, 2))
local a,b = debug.getlocal(co, 1, 1)
assert(a == "x" and b == 10)
a,b = debug.getlocal(co, 1, 2)
assert(a == "a" and b == 1)
debug.setlocal(co, 1, 2, "hi")
assert(debug.gethook(co) == foo)
assert(#tr == 2 and
tr[1] == l.currentline-1 and tr[2] == l.currentline)
a,b,c = pcall(coroutine.resume, co)
assert(a and b and c == l.currentline+1)
checktraceback(co, {"yield", "in function <"})
a,b = coroutine.resume(co)
assert(a and b == "hi")
assert(#tr == 4 and tr[4] == l.currentline+2)
assert(debug.gethook(co) == foo)
assert(not debug.gethook())
checktraceback(co, {})
-- check get/setlocal in coroutines
co = coroutine.create(function (x)
local a, b = coroutine.yield(x)
assert(a == 100 and b == nil)
return x
end)
a, b = coroutine.resume(co, 10)
assert(a and b == 10)
a, b = debug.getlocal(co, 1, 1)
assert(a == "x" and b == 10)
assert(not debug.getlocal(co, 1, 5))
assert(debug.setlocal(co, 1, 1, 30) == "x")
assert(not debug.setlocal(co, 1, 5, 40))
a, b = coroutine.resume(co, 100)
assert(a and b == 30)
-- check traceback of suspended (or dead with error) coroutines
function f(i) if i==0 then error(i) else coroutine.yield(); f(i-1) end end
co = coroutine.create(function (x) f(x) end)
a, b = coroutine.resume(co, 3)
t = {"'coroutine.yield'", "'f'", "in function <"}
while coroutine.status(co) == "suspended" do
checktraceback(co, t)
a, b = coroutine.resume(co)
table.insert(t, 2, "'f'") -- one more recursive call to 'f'
end
t[1] = "'error'"
checktraceback(co, t)
-- test acessing line numbers of a coroutine from a resume inside
-- a C function (this is a known bug in Lua 5.0)
local function g(x)
coroutine.yield(x)
end
local function f (i)
debug.sethook(function () end, "l")
for j=1,1000 do
g(i+j)
end
end
local co = coroutine.wrap(f)
co(10)
pcall(co)
pcall(co)
assert(type(debug.getregistry()) == "table")
-- test tagmethod information
local a = {}
local function f (t)
local info = debug.getinfo(1);
assert(info.namewhat == "metamethod")
a.op = info.name
return info.name
end
setmetatable(a, {
__index = f; __add = f; __div = f; __mod = f; __concat = f; __pow = f;
__mul = f; __idiv = f; __unm = f; __len = f; __sub = f;
__shl = f; __shr = f; __bor = f; __bxor = f;
__eq = f; __le = f; __lt = f; __unm = f; __len = f; __band = f;
__bnot = f;
})
local b = setmetatable({}, getmetatable(a))
assert(a[3] == "__index" and a^3 == "__pow" and a..a == "__concat")
assert(a/3 == "__div" and 3%a == "__mod")
assert(a+3 == "__add" and 3-a == "__sub" and a*3 == "__mul" and
-a == "__unm" and #a == "__len" and a&3 == "__band")
assert(a|3 == "__bor" and 3~a == "__bxor" and a<<3 == "__shl" and
a>>1 == "__shr")
assert (a==b and a.op == "__eq")
assert (a>=b and a.op == "__le")
assert (a>b and a.op == "__lt")
assert(~a == "__bnot")
do -- testing for-iterator name
local function f()
assert(debug.getinfo(1).name == "for iterator")
end
for i in f do end
end
do -- testing debug info for finalizers
local name = nil
-- create a piece of garbage with a finalizer
setmetatable({}, {__gc = function ()
local t = debug.getinfo(2) -- get callee information
assert(t.namewhat == "metamethod")
name = t.name
end})
-- repeat until previous finalizer runs (setting 'name')
repeat local a = {} until name
assert(name == "__gc")
end
do
print("testing traceback sizes")
local function countlines (s)
return select(2, string.gsub(s, "\n", ""))
end
local function deep (lvl, n)
if lvl == 0 then
return (debug.traceback("message", n))
else
return (deep(lvl-1, n))
end
end
local function checkdeep (total, start)
local s = deep(total, start)
local rest = string.match(s, "^message\nstack traceback:\n(.*)$")
local cl = countlines(rest)
-- at most 10 lines in first part, 11 in second, plus '...'
assert(cl <= 10 + 11 + 1)
local brk = string.find(rest, "%.%.%.")
if brk then -- does message have '...'?
local rest1 = string.sub(rest, 1, brk)
local rest2 = string.sub(rest, brk, #rest)
assert(countlines(rest1) == 10 and countlines(rest2) == 11)
else
assert(cl == total - start + 2)
end
end
for d = 1, 51, 10 do
for l = 1, d do
-- use coroutines to ensure complete control of the stack
coroutine.wrap(checkdeep)(d, l)
end
end
end
print("testing debug functions on chunk without debug info")
prog = [[-- program to be loaded without debug information
local debug = require'debug'
local a = 12 -- a local variable
local n, v = debug.getlocal(1, 1)
assert(n == "(*temporary)" and v == debug) -- unkown name but known value
n, v = debug.getlocal(1, 2)
assert(n == "(*temporary)" and v == 12) -- unkown name but known value
-- a function with an upvalue
local f = function () local x; return a end
n, v = debug.getupvalue(f, 1)
assert(n == "(*no name)" and v == 12)
assert(debug.setupvalue(f, 1, 13) == "(*no name)")
assert(a == 13)
local t = debug.getinfo(f)
assert(t.name == nil and t.linedefined > 0 and
t.lastlinedefined == t.linedefined and
t.short_src == "?")
assert(debug.getinfo(1).currentline == -1)
t = debug.getinfo(f, "L").activelines
assert(next(t) == nil) -- active lines are empty
-- dump/load a function without debug info
f = load(string.dump(f))
t = debug.getinfo(f)
assert(t.name == nil and t.linedefined > 0 and
t.lastlinedefined == t.linedefined and
t.short_src == "?")
assert(debug.getinfo(1).currentline == -1)
return a
]]
-- load 'prog' without debug info
local f = assert(load(string.dump(load(prog), true)))
assert(f() == 13)
do -- tests for 'source' in binary dumps
local prog = [[
return function (x)
return function (y)
return x + y
end
end
]]
local name = string.rep("x", 1000)
local p = assert(load(prog, name))
-- load 'p' as a binary chunk with debug information
local c = string.dump(p)
assert(#c > 1000 and #c < 2000) -- no repetition of 'source' in dump
local f = assert(load(c))
local g = f()
local h = g(3)
assert(h(5) == 8)
assert(debug.getinfo(f).source == name and -- all functions have 'source'
debug.getinfo(g).source == name and
debug.getinfo(h).source == name)
-- again, without debug info
local c = string.dump(p, true)
assert(#c < 500) -- no 'source' in dump
local f = assert(load(c))
local g = f()
local h = g(30)
assert(h(50) == 80)
assert(debug.getinfo(f).source == '=?' and -- no function has 'source'
debug.getinfo(g).source == '=?' and
debug.getinfo(h).source == '=?')
end
print"OK"

@ -0,0 +1,537 @@
-- $Id: errors.lua,v 1.94 2016/12/21 19:23:02 roberto Exp $
-- See Copyright Notice in file all.lua
print("testing errors")
local debug = require"debug"
-- avoid problems with 'strict' module (which may generate other error messages)
local mt = getmetatable(_G) or {}
local oldmm = mt.__index
mt.__index = nil
local function checkerr (msg, f, ...)
local st, err = pcall(f, ...)
assert(not st and string.find(err, msg))
end
local function doit (s)
local f, msg = load(s)
if f == nil then return msg end
local cond, msg = pcall(f)
return (not cond) and msg
end
local function checkmessage (prog, msg)
local m = doit(prog)
assert(string.find(m, msg, 1, true))
end
local function checksyntax (prog, extra, token, line)
local msg = doit(prog)
if not string.find(token, "^<%a") and not string.find(token, "^char%(")
then token = "'"..token.."'" end
token = string.gsub(token, "(%p)", "%%%1")
local pt = string.format([[^%%[string ".*"%%]:%d: .- near %s$]],
line, token)
assert(string.find(msg, pt))
assert(string.find(msg, msg, 1, true))
end
-- test error message with no extra info
assert(doit("error('hi', 0)") == 'hi')
-- test error message with no info
assert(doit("error()") == nil)
-- test common errors/errors that crashed in the past
assert(doit("table.unpack({}, 1, n=2^30)"))
assert(doit("a=math.sin()"))
assert(not doit("tostring(1)") and doit("tostring()"))
assert(doit"tonumber()")
assert(doit"repeat until 1; a")
assert(doit"return;;")
assert(doit"assert(false)")
assert(doit"assert(nil)")
assert(doit("function a (... , ...) end"))
assert(doit("function a (, ...) end"))
assert(doit("local t={}; t = t[#t] + 1"))
checksyntax([[
local a = {4
]], "'}' expected (to close '{' at line 1)", "<eof>", 3)
-- tests for better error messages
checkmessage("a = {} + 1", "arithmetic")
checkmessage("a = {} | 1", "bitwise operation")
checkmessage("a = {} < 1", "attempt to compare")
checkmessage("a = {} <= 1", "attempt to compare")
checkmessage("a=1; bbbb=2; a=math.sin(3)+bbbb(3)", "global 'bbbb'")
checkmessage("a={}; do local a=1 end a:bbbb(3)", "method 'bbbb'")
checkmessage("local a={}; a.bbbb(3)", "field 'bbbb'")
assert(not string.find(doit"a={13}; local bbbb=1; a[bbbb](3)", "'bbbb'"))
checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number")
checkmessage("a=(1)..{}", "a table value")
checkmessage("a = #print", "length of a function value")
checkmessage("a = #3", "length of a number value")
aaa = nil
checkmessage("aaa.bbb:ddd(9)", "global 'aaa'")
checkmessage("local aaa={bbb=1}; aaa.bbb:ddd(9)", "field 'bbb'")
checkmessage("local aaa={bbb={}}; aaa.bbb:ddd(9)", "method 'ddd'")
checkmessage("local a,b,c; (function () a = b+1 end)()", "upvalue 'b'")
assert(not doit"local aaa={bbb={ddd=next}}; aaa.bbb:ddd(nil)")
-- upvalues being indexed do not go to the stack
checkmessage("local a,b,cc; (function () a = cc[1] end)()", "upvalue 'cc'")
checkmessage("local a,b,cc; (function () a.x = 1 end)()", "upvalue 'a'")
checkmessage("local _ENV = {x={}}; a = a + 1", "global 'a'")
checkmessage("b=1; local aaa='a'; x=aaa+b", "local 'aaa'")
checkmessage("aaa={}; x=3/aaa", "global 'aaa'")
checkmessage("aaa='2'; b=nil;x=aaa*b", "global 'b'")
checkmessage("aaa={}; x=-aaa", "global 'aaa'")
-- short circuit
checkmessage("a=1; local a,bbbb=2,3; a = math.sin(1) and bbbb(3)",
"local 'bbbb'")
checkmessage("a=1; local a,bbbb=2,3; a = bbbb(1) or a(3)", "local 'bbbb'")
checkmessage("local a,b,c,f = 1,1,1; f((a and b) or c)", "local 'f'")
checkmessage("local a,b,c = 1,1,1; ((a and b) or c)()", "call a number value")
assert(not string.find(doit"aaa={}; x=(aaa or aaa)+(aaa and aaa)", "'aaa'"))
assert(not string.find(doit"aaa={}; (aaa or aaa)()", "'aaa'"))
checkmessage("print(print < 10)", "function with number")
checkmessage("print(print < print)", "two function values")
checkmessage("print('10' < 10)", "string with number")
checkmessage("print(10 < '23')", "number with string")
-- float->integer conversions
checkmessage("local a = 2.0^100; x = a << 2", "local a")
checkmessage("local a = 1 >> 2.0^100", "has no integer representation")
checkmessage("local a = '10' << 2.0^100", "has no integer representation")
checkmessage("local a = 2.0^100 & 1", "has no integer representation")
checkmessage("local a = 2.0^100 & '1'", "has no integer representation")
checkmessage("local a = 2.0 | 1e40", "has no integer representation")
checkmessage("local a = 2e100 ~ 1", "has no integer representation")
checkmessage("string.sub('a', 2.0^100)", "has no integer representation")
checkmessage("string.rep('a', 3.3)", "has no integer representation")
checkmessage("return 6e40 & 7", "has no integer representation")
checkmessage("return 34 << 7e30", "has no integer representation")
checkmessage("return ~-3e40", "has no integer representation")
checkmessage("return ~-3.009", "has no integer representation")
checkmessage("return 3.009 & 1", "has no integer representation")
checkmessage("return 34 >> {}", "table value")
checkmessage("a = 24 // 0", "divide by zero")
checkmessage("a = 1 % 0", "'n%0'")
-- passing light userdata instead of full userdata
_G.D = debug
checkmessage([[
-- create light udata
local x = D.upvalueid(function () return debug end, 1)
D.setuservalue(x, {})
]], "light userdata")
_G.D = nil
do -- named objects (field '__name')
checkmessage("math.sin(io.input())", "(number expected, got FILE*)")
_G.XX = setmetatable({}, {__name = "My Type"})
assert(string.find(tostring(XX), "^My Type"))
checkmessage("io.input(XX)", "(FILE* expected, got My Type)")
checkmessage("return XX + 1", "on a My Type value")
checkmessage("return ~io.stdin", "on a FILE* value")
checkmessage("return XX < XX", "two My Type values")
checkmessage("return {} < XX", "table with My Type")
checkmessage("return XX < io.stdin", "My Type with FILE*")
_G.XX = nil
end
-- global functions
checkmessage("(io.write or print){}", "io.write")
checkmessage("(collectgarbage or print){}", "collectgarbage")
-- errors in functions without debug info
do
local f = function (a) return a + 1 end
f = assert(load(string.dump(f, true)))
assert(f(3) == 4)
checkerr("^%?:%-1:", f, {})
-- code with a move to a local var ('OP_MOV A B' with A<B)
f = function () local a; a = {}; return a + 2 end
-- no debug info (so that 'a' is unknown)
f = assert(load(string.dump(f, true)))
-- symbolic execution should not get lost
checkerr("^%?:%-1:.*table value", f)
end
-- tests for field accesses after RK limit
local t = {}
for i = 1, 1000 do
t[i] = "a = x" .. i
end
local s = table.concat(t, "; ")
t = nil
checkmessage(s.."; a = bbb + 1", "global 'bbb'")
checkmessage("local _ENV=_ENV;"..s.."; a = bbb + 1", "global 'bbb'")
checkmessage(s.."; local t = {}; a = t.bbb + 1", "field 'bbb'")
checkmessage(s.."; local t = {}; t:bbb()", "method 'bbb'")
checkmessage([[aaa=9
repeat until 3==3
local x=math.sin(math.cos(3))
if math.sin(1) == x then return math.sin(1) end -- tail call
local a,b = 1, {
{x='a'..'b'..'c', y='b', z=x},
{1,2,3,4,5} or 3+3<=3+3,
3+1>3+1,
{d = x and aaa[x or y]}}
]], "global 'aaa'")
checkmessage([[
local x,y = {},1
if math.sin(1) == 0 then return 3 end -- return
x.a()]], "field 'a'")
checkmessage([[
prefix = nil
insert = nil
while 1 do
local a
if nil then break end
insert(prefix, a)
end]], "global 'insert'")
checkmessage([[ -- tail call
return math.sin("a")
]], "'sin'")
checkmessage([[collectgarbage("nooption")]], "invalid option")
checkmessage([[x = print .. "a"]], "concatenate")
checkmessage([[x = "a" .. false]], "concatenate")
checkmessage([[x = {} .. 2]], "concatenate")
checkmessage("getmetatable(io.stdin).__gc()", "no value")
checkmessage([[
local Var
local function main()
NoSuchName (function() Var=0 end)
end
main()
]], "global 'NoSuchName'")
print'+'
a = {}; setmetatable(a, {__index = string})
checkmessage("a:sub()", "bad self")
checkmessage("string.sub('a', {})", "#2")
checkmessage("('a'):sub{}", "#1")
checkmessage("table.sort({1,2,3}, table.sort)", "'table.sort'")
checkmessage("string.gsub('s', 's', setmetatable)", "'setmetatable'")
-- tests for errors in coroutines
local function f (n)
local c = coroutine.create(f)
local a,b = coroutine.resume(c)
return b
end
assert(string.find(f(), "C stack overflow"))
checkmessage("coroutine.yield()", "outside a coroutine")
f = coroutine.wrap(function () table.sort({1,2,3}, coroutine.yield) end)
checkerr("yield across", f)
-- testing size of 'source' info; size of buffer for that info is
-- LUA_IDSIZE, declared as 60 in luaconf. Get one position for '\0'.
idsize = 60 - 1
local function checksize (source)
-- syntax error
local _, msg = load("x", source)
msg = string.match(msg, "^([^:]*):") -- get source (1st part before ':')
assert(msg:len() <= idsize)
end
for i = 60 - 10, 60 + 10 do -- check border cases around 60
checksize("@" .. string.rep("x", i)) -- file names
checksize(string.rep("x", i - 10)) -- string sources
checksize("=" .. string.rep("x", i)) -- exact sources
end
-- testing line error
local function lineerror (s, l)
local err,msg = pcall(load(s))
local line = string.match(msg, ":(%d+):")
assert((line and line+0) == l)
end
lineerror("local a\n for i=1,'a' do \n print(i) \n end", 2)
lineerror("\n local a \n for k,v in 3 \n do \n print(k) \n end", 3)
lineerror("\n\n for k,v in \n 3 \n do \n print(k) \n end", 4)
lineerror("function a.x.y ()\na=a+1\nend", 1)
lineerror("a = \na\n+\n{}", 3)
lineerror("a = \n3\n+\n(\n4\n/\nprint)", 6)
lineerror("a = \nprint\n+\n(\n4\n/\n7)", 3)
lineerror("a\n=\n-\n\nprint\n;", 3)
lineerror([[
a
(
23)
]], 1)
lineerror([[
local a = {x = 13}
a
.
x
(
23
)
]], 2)
lineerror([[
local a = {x = 13}
a
.
x
(
23 + a
)
]], 6)
local p = [[
function g() f() end
function f(x) error('a', X) end
g()
]]
X=3;lineerror((p), 3)
X=0;lineerror((p), nil)
X=1;lineerror((p), 2)
X=2;lineerror((p), 1)
if not _soft then
-- several tests that exaust the Lua stack
collectgarbage()
print"testing stack overflow"
C = 0
local l = debug.getinfo(1, "l").currentline; function y () C=C+1; y() end
local function checkstackmessage (m)
return (string.find(m, "^.-:%d+: stack overflow"))
end
-- repeated stack overflows (to check stack recovery)
assert(checkstackmessage(doit('y()')))
print('+')
assert(checkstackmessage(doit('y()')))
print('+')
assert(checkstackmessage(doit('y()')))
print('+')
-- error lines in stack overflow
C = 0
local l1
local function g(x)
l1 = debug.getinfo(x, "l").currentline; y()
end
local _, stackmsg = xpcall(g, debug.traceback, 1)
print('+')
local stack = {}
for line in string.gmatch(stackmsg, "[^\n]*") do
local curr = string.match(line, ":(%d+):")
if curr then table.insert(stack, tonumber(curr)) end
end
local i=1
while stack[i] ~= l1 do
assert(stack[i] == l)
i = i+1
end
assert(i > 15)
-- error in error handling
local res, msg = xpcall(error, error)
assert(not res and type(msg) == 'string')
print('+')
local function f (x)
if x==0 then error('a\n')
else
local aux = function () return f(x-1) end
local a,b = xpcall(aux, aux)
return a,b
end
end
f(3)
local function loop (x,y,z) return 1 + loop(x, y, z) end
local res, msg = xpcall(loop, function (m)
assert(string.find(m, "stack overflow"))
checkerr("error handling", loop)
assert(math.sin(0) == 0)
return 15
end)
assert(msg == 15)
local f = function ()
for i = 999900, 1000000, 1 do table.unpack({}, 1, i) end
end
checkerr("too many results", f)
end
do
-- non string messages
local t = {}
local res, msg = pcall(function () error(t) end)
assert(not res and msg == t)
res, msg = pcall(function () error(nil) end)
assert(not res and msg == nil)
local function f() error{msg='x'} end
res, msg = xpcall(f, function (r) return {msg=r.msg..'y'} end)
assert(msg.msg == 'xy')
-- 'assert' with extra arguments
res, msg = pcall(assert, false, "X", t)
assert(not res and msg == "X")
-- 'assert' with no message
res, msg = pcall(function () assert(false) end)
local line = string.match(msg, "%w+%.lua:(%d+): assertion failed!$")
assert(tonumber(line) == debug.getinfo(1, "l").currentline - 2)
-- 'assert' with non-string messages
res, msg = pcall(assert, false, t)
assert(not res and msg == t)
res, msg = pcall(assert, nil, nil)
assert(not res and msg == nil)
-- 'assert' without arguments
res, msg = pcall(assert)
assert(not res and string.find(msg, "value expected"))
end
-- xpcall with arguments
a, b, c = xpcall(string.find, error, "alo", "al")
assert(a and b == 1 and c == 2)
a, b, c = xpcall(string.find, function (x) return {} end, true, "al")
assert(not a and type(b) == "table" and c == nil)
print("testing tokens in error messages")
checksyntax("syntax error", "", "error", 1)
checksyntax("1.000", "", "1.000", 1)
checksyntax("[[a]]", "", "[[a]]", 1)
checksyntax("'aa'", "", "'aa'", 1)
checksyntax("while << do end", "", "<<", 1)
checksyntax("for >> do end", "", ">>", 1)
-- test invalid non-printable char in a chunk
checksyntax("a\1a = 1", "", "<\\1>", 1)
-- test 255 as first char in a chunk
checksyntax("\255a = 1", "", "<\\255>", 1)
doit('I = load("a=9+"); a=3')
assert(a==3 and I == nil)
print('+')
lim = 1000
if _soft then lim = 100 end
for i=1,lim do
doit('a = ')
doit('a = 4+nil')
end
-- testing syntax limits
local maxClevel = 200 -- LUAI_MAXCCALLS (in llimits.h)
local function testrep (init, rep, close, repc)
local s = init .. string.rep(rep, maxClevel - 10) .. close ..
string.rep(repc, maxClevel - 10)
assert(load(s)) -- 190 levels is OK
s = init .. string.rep(rep, maxClevel + 1)
checkmessage(s, "too many C levels")
end
testrep("local a; a", ",a", "= 1", ",1") -- multiple assignment
testrep("local a; a=", "{", "0", "}")
testrep("local a; a=", "(", "2", ")")
testrep("local a; ", "a(", "2", ")")
testrep("", "do ", "", " end")
testrep("", "while a do ", "", " end")
testrep("local a; ", "if a then else ", "", " end")
testrep("", "function foo () ", "", " end")
testrep("local a; a=", "a..", "a", "")
testrep("local a; a=", "a^", "a", "")
checkmessage("a = f(x" .. string.rep(",x", 260) .. ")", "too many registers")
-- testing other limits
-- upvalues
local lim = 127
local s = "local function fooA ()\n local "
for j = 1,lim do
s = s.."a"..j..", "
end
s = s.."b,c\n"
s = s.."local function fooB ()\n local "
for j = 1,lim do
s = s.."b"..j..", "
end
s = s.."b\n"
s = s.."function fooC () return b+c"
local c = 1+2
for j = 1,lim do
s = s.."+a"..j.."+b"..j
c = c + 2
end
s = s.."\nend end end"
local a,b = load(s)
assert(c > 255 and string.find(b, "too many upvalues") and
string.find(b, "line 5"))
-- local variables
s = "\nfunction foo ()\n local "
for j = 1,300 do
s = s.."a"..j..", "
end
s = s.."b\n"
local a,b = load(s)
assert(string.find(b, "line 2") and string.find(b, "too many local variables"))
mt.__index = oldmm
print('OK')

@ -0,0 +1,456 @@
-- $Id: events.lua,v 1.45 2016/12/21 19:23:02 roberto Exp $
-- See Copyright Notice in file all.lua
print('testing metatables')
-- local debug = require'debug'
X = 20; B = 30
_ENV = setmetatable({}, {__index=_G})
collectgarbage()
X = X+10
assert(X == 30 and _G.X == 20)
B = false
assert(B == false)
B = nil
assert(B == 30)
assert(getmetatable{} == nil)
assert(getmetatable(4) == nil)
assert(getmetatable(nil) == nil)
a={name = "NAME"}; setmetatable(a, {__metatable = "xuxu",
__tostring=function(x) return x.name end})
assert(getmetatable(a) == "xuxu")
assert(tostring(a) == "NAME")
-- cannot change a protected metatable
assert(pcall(setmetatable, a, {}) == false)
a.name = "gororoba"
assert(tostring(a) == "gororoba")
local a, t = {10,20,30; x="10", y="20"}, {}
assert(setmetatable(a,t) == a)
assert(getmetatable(a) == t)
assert(setmetatable(a,nil) == a)
assert(getmetatable(a) == nil)
assert(setmetatable(a,t) == a)
function f (t, i, e)
assert(not e)
local p = rawget(t, "parent")
return (p and p[i]+3), "dummy return"
end
t.__index = f
a.parent = {z=25, x=12, [4] = 24}
assert(a[1] == 10 and a.z == 28 and a[4] == 27 and a.x == "10")
collectgarbage()
a = setmetatable({}, t)
function f(t, i, v) rawset(t, i, v-3) end
setmetatable(t, t) -- causes a bug in 5.1 !
t.__newindex = f
a[1] = 30; a.x = "101"; a[5] = 200
assert(a[1] == 27 and a.x == 98 and a[5] == 197)
do -- bug in Lua 5.3.2
local mt = {}
mt.__newindex = mt
local t = setmetatable({}, mt)
t[1] = 10 -- will segfault on some machines
assert(mt[1] == 10)
end
local c = {}
a = setmetatable({}, t)
t.__newindex = c
a[1] = 10; a[2] = 20; a[3] = 90
assert(c[1] == 10 and c[2] == 20 and c[3] == 90)
do
local a;
a = setmetatable({}, {__index = setmetatable({},
{__index = setmetatable({},
{__index = function (_,n) return a[n-3]+4, "lixo" end})})})
a[0] = 20
for i=0,10 do
assert(a[i*3] == 20 + i*4)
end
end
do -- newindex
local foi
local a = {}
for i=1,10 do a[i] = 0; a['a'..i] = 0; end
setmetatable(a, {__newindex = function (t,k,v) foi=true; rawset(t,k,v) end})
foi = false; a[1]=0; assert(not foi)
foi = false; a['a1']=0; assert(not foi)
foi = false; a['a11']=0; assert(foi)
foi = false; a[11]=0; assert(foi)
foi = false; a[1]=nil; assert(not foi)
foi = false; a[1]=nil; assert(foi)
end
setmetatable(t, nil)
function f (t, ...) return t, {...} end
t.__call = f
do
local x,y = a(table.unpack{'a', 1})
assert(x==a and y[1]=='a' and y[2]==1 and y[3]==nil)
x,y = a()
assert(x==a and y[1]==nil)
end
local b = setmetatable({}, t)
setmetatable(b,t)
function f(op)
return function (...) cap = {[0] = op, ...} ; return (...) end
end
t.__add = f("add")
t.__sub = f("sub")
t.__mul = f("mul")
t.__div = f("div")
t.__idiv = f("idiv")
t.__mod = f("mod")
t.__unm = f("unm")
t.__pow = f("pow")
t.__len = f("len")
t.__band = f("band")
t.__bor = f("bor")
t.__bxor = f("bxor")
t.__shl = f("shl")
t.__shr = f("shr")
t.__bnot = f("bnot")
assert(b+5 == b)
assert(cap[0] == "add" and cap[1] == b and cap[2] == 5 and cap[3]==nil)
assert(b+'5' == b)
assert(cap[0] == "add" and cap[1] == b and cap[2] == '5' and cap[3]==nil)
assert(5+b == 5)
assert(cap[0] == "add" and cap[1] == 5 and cap[2] == b and cap[3]==nil)
assert('5'+b == '5')
assert(cap[0] == "add" and cap[1] == '5' and cap[2] == b and cap[3]==nil)
b=b-3; assert(getmetatable(b) == t)
assert(5-a == 5)
assert(cap[0] == "sub" and cap[1] == 5 and cap[2] == a and cap[3]==nil)
assert('5'-a == '5')
assert(cap[0] == "sub" and cap[1] == '5' and cap[2] == a and cap[3]==nil)
assert(a*a == a)
assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==nil)
assert(a/0 == a)
assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==nil)
assert(a%2 == a)
assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==nil)
assert(a // (1/0) == a)
assert(cap[0] == "idiv" and cap[1] == a and cap[2] == 1/0 and cap[3]==nil)
assert(a & "hi" == a)
assert(cap[0] == "band" and cap[1] == a and cap[2] == "hi" and cap[3]==nil)
assert(a | "hi" == a)
assert(cap[0] == "bor" and cap[1] == a and cap[2] == "hi" and cap[3]==nil)
assert("hi" ~ a == "hi")
assert(cap[0] == "bxor" and cap[1] == "hi" and cap[2] == a and cap[3]==nil)
assert(-a == a)
assert(cap[0] == "unm" and cap[1] == a)
assert(a^4 == a)
assert(cap[0] == "pow" and cap[1] == a and cap[2] == 4 and cap[3]==nil)
assert(a^'4' == a)
assert(cap[0] == "pow" and cap[1] == a and cap[2] == '4' and cap[3]==nil)
assert(4^a == 4)
assert(cap[0] == "pow" and cap[1] == 4 and cap[2] == a and cap[3]==nil)
assert('4'^a == '4')
assert(cap[0] == "pow" and cap[1] == '4' and cap[2] == a and cap[3]==nil)
assert(#a == a)
assert(cap[0] == "len" and cap[1] == a)
assert(~a == a)
assert(cap[0] == "bnot" and cap[1] == a)
assert(a << 3 == a)
assert(cap[0] == "shl" and cap[1] == a and cap[2] == 3)
assert(1.5 >> a == 1.5)
assert(cap[0] == "shr" and cap[1] == 1.5 and cap[2] == a)
-- test for rawlen
t = setmetatable({1,2,3}, {__len = function () return 10 end})
assert(#t == 10 and rawlen(t) == 3)
assert(rawlen"abc" == 3)
assert(not pcall(rawlen, io.stdin))
assert(not pcall(rawlen, 34))
assert(not pcall(rawlen))
-- rawlen for long strings
assert(rawlen(string.rep('a', 1000)) == 1000)
t = {}
t.__lt = function (a,b,c)
collectgarbage()
assert(c == nil)
if type(a) == 'table' then a = a.x end
if type(b) == 'table' then b = b.x end
return a<b, "dummy"
end
function Op(x) return setmetatable({x=x}, t) end
local function test ()
assert(not(Op(1)<Op(1)) and (Op(1)<Op(2)) and not(Op(2)<Op(1)))
assert(not(1 < Op(1)) and (Op(1) < 2) and not(2 < Op(1)))
assert(not(Op('a')<Op('a')) and (Op('a')<Op('b')) and not(Op('b')<Op('a')))
assert(not('a' < Op('a')) and (Op('a') < 'b') and not(Op('b') < Op('a')))
assert((Op(1)<=Op(1)) and (Op(1)<=Op(2)) and not(Op(2)<=Op(1)))
assert((Op('a')<=Op('a')) and (Op('a')<=Op('b')) and not(Op('b')<=Op('a')))
assert(not(Op(1)>Op(1)) and not(Op(1)>Op(2)) and (Op(2)>Op(1)))
assert(not(Op('a')>Op('a')) and not(Op('a')>Op('b')) and (Op('b')>Op('a')))
assert((Op(1)>=Op(1)) and not(Op(1)>=Op(2)) and (Op(2)>=Op(1)))
assert((1 >= Op(1)) and not(1 >= Op(2)) and (Op(2) >= 1))
assert((Op('a')>=Op('a')) and not(Op('a')>=Op('b')) and (Op('b')>=Op('a')))
assert(('a' >= Op('a')) and not(Op('a') >= 'b') and (Op('b') >= Op('a')))
end
test()
t.__le = function (a,b,c)
assert(c == nil)
if type(a) == 'table' then a = a.x end
if type(b) == 'table' then b = b.x end
return a<=b, "dummy"
end
test() -- retest comparisons, now using both `lt' and `le'
-- test `partial order'
local function rawSet(x)
local y = {}
for _,k in pairs(x) do y[k] = 1 end
return y
end
local function Set(x)
return setmetatable(rawSet(x), t)
end
t.__lt = function (a,b)
for k in pairs(a) do
if not b[k] then return false end
b[k] = nil
end
return next(b) ~= nil
end
t.__le = nil
assert(Set{1,2,3} < Set{1,2,3,4})
assert(not(Set{1,2,3,4} < Set{1,2,3,4}))
assert((Set{1,2,3,4} <= Set{1,2,3,4}))
assert((Set{1,2,3,4} >= Set{1,2,3,4}))
assert((Set{1,3} <= Set{3,5})) -- wrong!! model needs a `le' method ;-)
t.__le = function (a,b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end
assert(not (Set{1,3} <= Set{3,5})) -- now its OK!
assert(not(Set{1,3} <= Set{3,5}))
assert(not(Set{1,3} >= Set{3,5}))
t.__eq = function (a,b)
for k in pairs(a) do
if not b[k] then return false end
b[k] = nil
end
return next(b) == nil
end
local s = Set{1,3,5}
assert(s == Set{3,5,1})
assert(not rawequal(s, Set{3,5,1}))
assert(rawequal(s, s))
assert(Set{1,3,5,1} == rawSet{3,5,1})
assert(rawSet{1,3,5,1} == Set{3,5,1})
assert(Set{1,3,5} ~= Set{3,5,1,6})
-- '__eq' is not used for table accesses
t[Set{1,3,5}] = 1
assert(t[Set{1,3,5}] == nil)
if not T then
(Message or print)('\n >>> testC not active: skipping tests for \z
userdata equality <<<\n')
else
local u1 = T.newuserdata(0)
local u2 = T.newuserdata(0)
local u3 = T.newuserdata(0)
assert(u1 ~= u2 and u1 ~= u3)
-- debug.setuservalue(u1, 1);
-- debug.setuservalue(u2, 2);
-- debug.setuservalue(u3, 1);
-- debug.setmetatable(u1, {__eq = function (a, b)
-- return debug.getuservalue(a) == debug.getuservalue(b)
-- end})
-- debug.setmetatable(u2, {__eq = function (a, b)
-- return true
-- end})
-- assert(u1 == u3 and u3 == u1 and u1 ~= u2)
-- assert(u2 == u1 and u2 == u3 and u3 == u2)
-- assert(u2 ~= {}) -- different types cannot be equal
end
t.__concat = function (a,b,c)
assert(c == nil)
if type(a) == 'table' then a = a.val end
if type(b) == 'table' then b = b.val end
if A then return a..b
else
return setmetatable({val=a..b}, t)
end
end
c = {val="c"}; setmetatable(c, t)
d = {val="d"}; setmetatable(d, t)
A = true
assert(c..d == 'cd')
assert(0 .."a".."b"..c..d.."e".."f"..(5+3).."g" == "0abcdef8g")
A = false
assert((c..d..c..d).val == 'cdcd')
x = c..d
assert(getmetatable(x) == t and x.val == 'cd')
x = 0 .."a".."b"..c..d.."e".."f".."g"
assert(x.val == "0abcdefg")
-- concat metamethod x numbers (bug in 5.1.1)
c = {}
local x
setmetatable(c, {__concat = function (a,b)
assert(type(a) == "number" and b == c or type(b) == "number" and a == c)
return c
end})
assert(c..5 == c and 5 .. c == c)
assert(4 .. c .. 5 == c and 4 .. 5 .. 6 .. 7 .. c == c)
-- test comparison compatibilities
local t1, t2, c, d
t1 = {}; c = {}; setmetatable(c, t1)
d = {}
t1.__eq = function () return true end
t1.__lt = function () return true end
setmetatable(d, t1)
assert(c == d and c < d and not(d <= c))
t2 = {}
t2.__eq = t1.__eq
t2.__lt = t1.__lt
setmetatable(d, t2)
assert(c == d and c < d and not(d <= c))
-- test for several levels of calls
local i
local tt = {
__call = function (t, ...)
i = i+1
if t.f then return t.f(...)
else return {...}
end
end
}
local a = setmetatable({}, tt)
local b = setmetatable({f=a}, tt)
local c = setmetatable({f=b}, tt)
i = 0
x = c(3,4,5)
assert(i == 3 and x[1] == 3 and x[3] == 5)
assert(_G.X == 20)
print'+'
local _g = _G
_ENV = setmetatable({}, {__index=function (_,k) return _g[k] end})
a = {}
rawset(a, "x", 1, 2, 3)
assert(a.x == 1 and rawget(a, "x", 3) == 1)
print '+'
-- testing metatables for basic types
-- mt = {__index = function (a,b) return a+b end,
-- __len = function (x) return math.floor(x) end}
-- debug.setmetatable(10, mt)
-- assert(getmetatable(-2) == mt)
-- assert((10)[3] == 13)
-- assert((10)["3"] == 13)
-- assert(#3.45 == 3)
-- debug.setmetatable(23, nil)
-- assert(getmetatable(-2) == nil)
-- debug.setmetatable(true, mt)
-- assert(getmetatable(false) == mt)
-- mt.__index = function (a,b) return a or b end
-- assert((true)[false] == true)
-- assert((false)[false] == false)
-- debug.setmetatable(false, nil)
-- assert(getmetatable(true) == nil)
-- debug.setmetatable(nil, mt)
-- assert(getmetatable(nil) == mt)
-- mt.__add = function (a,b) return (a or 0) + (b or 0) end
-- assert(10 + nil == 10)
-- assert(nil + 23 == 23)
-- assert(nil + nil == 0)
-- debug.setmetatable(nil, nil)
-- assert(getmetatable(nil) == nil)
-- debug.setmetatable(nil, {})
-- loops in delegation
a = {}; setmetatable(a, a); a.__index = a; a.__newindex = a
assert(not pcall(function (a,b) return a[b] end, a, 10))
assert(not pcall(function (a,b,c) a[b] = c end, a, 10, true))
-- bug in 5.1
T, K, V = nil
grandparent = {}
grandparent.__newindex = function(t,k,v) T=t; K=k; V=v end
parent = {}
parent.__newindex = parent
setmetatable(parent, grandparent)
child = setmetatable({}, parent)
child.foo = 10 --> CRASH (on some machines)
assert(T == parent and K == "foo" and V == 10)
print 'OK'
return 12

@ -0,0 +1,793 @@
-- $Id: files.lua,v 1.95 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
local debug = require "debug"
local maxint = math.maxinteger
assert(type(os.getenv"PATH") == "string")
assert(io.input(io.stdin) == io.stdin)
assert(not pcall(io.input, "non-existent-file"))
assert(io.output(io.stdout) == io.stdout)
local function testerr (msg, f, ...)
local stat, err = pcall(f, ...)
return (not stat and string.find(err, msg, 1, true))
end
local function checkerr (msg, f, ...)
assert(testerr(msg, f, ...))
end
-- cannot close standard files
assert(not io.close(io.stdin) and
not io.stdout:close() and
not io.stderr:close())
assert(type(io.input()) == "userdata" and io.type(io.output()) == "file")
assert(type(io.stdin) == "userdata" and io.type(io.stderr) == "file")
assert(not io.type(8))
local a = {}; setmetatable(a, {})
assert(not io.type(a))
assert(getmetatable(io.input()).__name == "FILE*")
local a,b,c = io.open('xuxu_nao_existe')
assert(not a and type(b) == "string" and type(c) == "number")
a,b,c = io.open('/a/b/c/d', 'w')
assert(not a and type(b) == "string" and type(c) == "number")
local file = os.tmpname()
local f, msg = io.open(file, "w")
if not f then
(Message or print)("'os.tmpname' file cannot be open; skipping file tests")
else --{ most tests here need tmpname
f:close()
print('testing i/o')
local otherfile = os.tmpname()
checkerr("invalid mode", io.open, file, "rw")
checkerr("invalid mode", io.open, file, "rb+")
checkerr("invalid mode", io.open, file, "r+bk")
checkerr("invalid mode", io.open, file, "")
checkerr("invalid mode", io.open, file, "+")
checkerr("invalid mode", io.open, file, "b")
assert(io.open(file, "r+b")):close()
assert(io.open(file, "r+")):close()
assert(io.open(file, "rb")):close()
assert(os.setlocale('C', 'all'))
io.input(io.stdin); io.output(io.stdout);
os.remove(file)
assert(not loadfile(file))
checkerr("", dofile, file)
assert(not io.open(file))
io.output(file)
assert(io.output() ~= io.stdout)
if not _port then -- invalid seek
local status, msg, code = io.stdin:seek("set", 1000)
assert(not status and type(msg) == "string" and type(code) == "number")
end
assert(io.output():seek() == 0)
assert(io.write("alo alo"):seek() == string.len("alo alo"))
assert(io.output():seek("cur", -3) == string.len("alo alo")-3)
assert(io.write("joao"))
assert(io.output():seek("end") == string.len("alo joao"))
assert(io.output():seek("set") == 0)
assert(io.write('"álo"', "{a}\n", "second line\n", "third line \n"))
assert(io.write('çfourth_line'))
io.output(io.stdout)
collectgarbage() -- file should be closed by GC
assert(io.input() == io.stdin and rawequal(io.output(), io.stdout))
print('+')
-- test GC for files
collectgarbage()
for i=1,120 do
for i=1,5 do
io.input(file)
assert(io.open(file, 'r'))
io.lines(file)
end
collectgarbage()
end
io.input():close()
io.close()
assert(os.rename(file, otherfile))
assert(not os.rename(file, otherfile))
io.output(io.open(otherfile, "ab"))
assert(io.write("\n\n\t\t ", 3450, "\n"));
io.close()
-- test writing/reading numbers
f = assert(io.open(file, "w"))
f:write(maxint, '\n')
f:write(string.format("0X%x\n", maxint))
f:write("0xABCp-3", '\n')
f:write(0, '\n')
f:write(-maxint, '\n')
f:write(string.format("0x%X\n", -maxint))
f:write("-0xABCp-3", '\n')
assert(f:close())
f = assert(io.open(file, "r"))
assert(f:read("n") == maxint)
assert(f:read("n") == maxint)
assert(f:read("n") == 0xABCp-3)
assert(f:read("n") == 0)
assert(f:read("*n") == -maxint) -- test old format (with '*')
assert(f:read("n") == -maxint)
assert(f:read("*n") == -0xABCp-3) -- test old format (with '*')
assert(f:close())
assert(os.remove(file))
-- test yielding during 'dofile'
f = assert(io.open(file, "w"))
f:write[[
local x, z = coroutine.yield(10)
local y = coroutine.yield(20)
return x + y * z
]]
assert(f:close())
f = coroutine.wrap(dofile)
assert(f(file) == 10)
print(f(100, 101) == 20)
assert(f(200) == 100 + 200 * 101)
assert(os.remove(file))
f = assert(io.open(file, "w"))
-- test number termination
f:write[[
-12.3- -0xffff+ .3|5.E-3X +234e+13E 0xDEADBEEFDEADBEEFx
0x1.13Ap+3e
]]
-- very long number
f:write("1234"); for i = 1, 1000 do f:write("0") end; f:write("\n")
-- invalid sequences (must read and discard valid prefixes)
f:write[[
.e+ 0.e; --; 0xX;
]]
assert(f:close())
f = assert(io.open(file, "r"))
assert(f:read("n") == -12.3); assert(f:read(1) == "-")
assert(f:read("n") == -0xffff); assert(f:read(2) == "+ ")
assert(f:read("n") == 0.3); assert(f:read(1) == "|")
assert(f:read("n") == 5e-3); assert(f:read(1) == "X")
assert(f:read("n") == 234e13); assert(f:read(1) == "E")
assert(f:read("n") == 0Xdeadbeefdeadbeef); assert(f:read(2) == "x\n")
assert(f:read("n") == 0x1.13aP3); assert(f:read(1) == "e")
do -- attempt to read too long number
assert(f:read("n") == nil) -- fails
local s = f:read("L") -- read rest of line
assert(string.find(s, "^00*\n$")) -- lots of 0's left
end
assert(not f:read("n")); assert(f:read(2) == "e+")
assert(not f:read("n")); assert(f:read(1) == ";")
assert(not f:read("n")); assert(f:read(2) == "-;")
assert(not f:read("n")); assert(f:read(1) == "X")
assert(not f:read("n")); assert(f:read(1) == ";")
assert(not f:read("n")); assert(not f:read(0)) -- end of file
assert(f:close())
assert(os.remove(file))
-- test line generators
assert(not pcall(io.lines, "non-existent-file"))
assert(os.rename(otherfile, file))
io.output(otherfile)
local n = 0
local f = io.lines(file)
while f() do n = n + 1 end;
assert(n == 6) -- number of lines in the file
checkerr("file is already closed", f)
checkerr("file is already closed", f)
-- copy from file to otherfile
n = 0
for l in io.lines(file) do io.write(l, "\n"); n = n + 1 end
io.close()
assert(n == 6)
-- copy from otherfile back to file
local f = assert(io.open(otherfile))
assert(io.type(f) == "file")
io.output(file)
assert(not io.output():read())
n = 0
for l in f:lines() do io.write(l, "\n"); n = n + 1 end
assert(tostring(f):sub(1, 5) == "file ")
assert(f:close()); io.close()
assert(n == 6)
checkerr("closed file", io.close, f)
assert(tostring(f) == "file (closed)")
assert(io.type(f) == "closed file")
io.input(file)
f = io.open(otherfile):lines()
n = 0
for l in io.lines() do assert(l == f()); n = n + 1 end
f = nil; collectgarbage()
assert(n == 6)
assert(os.remove(otherfile))
do -- bug in 5.3.1
io.output(otherfile)
io.write(string.rep("a", 300), "\n")
io.close()
local t ={}; for i = 1, 250 do t[i] = 1 end
t = {io.lines(otherfile, table.unpack(t))()}
-- everything ok here
assert(#t == 250 and t[1] == 'a' and t[#t] == 'a')
t[#t + 1] = 1 -- one too many
checkerr("too many arguments", io.lines, otherfile, table.unpack(t))
collectgarbage() -- ensure 'otherfile' is closed
assert(os.remove(otherfile))
end
io.input(file)
do -- test error returns
local a,b,c = io.input():write("xuxu")
assert(not a and type(b) == "string" and type(c) == "number")
end
checkerr("invalid format", io.read, "x")
assert(io.read(0) == "") -- not eof
assert(io.read(5, 'l') == '"álo"')
assert(io.read(0) == "")
assert(io.read() == "second line")
local x = io.input():seek()
assert(io.read() == "third line ")
assert(io.input():seek("set", x))
assert(io.read('L') == "third line \n")
assert(io.read(1) == "ç")
assert(io.read(string.len"fourth_line") == "fourth_line")
assert(io.input():seek("cur", -string.len"fourth_line"))
assert(io.read() == "fourth_line")
assert(io.read() == "") -- empty line
assert(io.read('n') == 3450)
assert(io.read(1) == '\n')
assert(io.read(0) == nil) -- end of file
assert(io.read(1) == nil) -- end of file
assert(io.read(30000) == nil) -- end of file
assert(({io.read(1)})[2] == nil)
assert(io.read() == nil) -- end of file
assert(({io.read()})[2] == nil)
assert(io.read('n') == nil) -- end of file
assert(({io.read('n')})[2] == nil)
assert(io.read('a') == '') -- end of file (OK for 'a')
assert(io.read('a') == '') -- end of file (OK for 'a')
collectgarbage()
print('+')
io.close(io.input())
checkerr(" input file is closed", io.read)
assert(os.remove(file))
local t = '0123456789'
for i=1,10 do t = t..t; end
assert(string.len(t) == 10*2^10)
io.output(file)
io.write("alo"):write("\n")
io.close()
checkerr(" output file is closed", io.write)
local f = io.open(file, "a+b")
io.output(f)
collectgarbage()
assert(io.write(' ' .. t .. ' '))
assert(io.write(';', 'end of file\n'))
f:flush(); io.flush()
f:close()
print('+')
io.input(file)
assert(io.read() == "alo")
assert(io.read(1) == ' ')
assert(io.read(string.len(t)) == t)
assert(io.read(1) == ' ')
assert(io.read(0))
assert(io.read('a') == ';end of file\n')
assert(io.read(0) == nil)
assert(io.close(io.input()))
-- test errors in read/write
do
local function ismsg (m)
-- error message is not a code number
return (type(m) == "string" and tonumber(m) == nil)
end
-- read
local f = io.open(file, "w")
local r, m, c = f:read()
assert(not r and ismsg(m) and type(c) == "number")
assert(f:close())
-- write
f = io.open(file, "r")
r, m, c = f:write("whatever")
assert(not r and ismsg(m) and type(c) == "number")
assert(f:close())
-- lines
f = io.open(file, "w")
r, m = pcall(f:lines())
assert(r == false and ismsg(m))
assert(f:close())
end
assert(os.remove(file))
-- test for L format
io.output(file); io.write"\n\nline\nother":close()
io.input(file)
assert(io.read"L" == "\n")
assert(io.read"L" == "\n")
assert(io.read"L" == "line\n")
assert(io.read"L" == "other")
assert(io.read"L" == nil)
io.input():close()
local f = assert(io.open(file))
local s = ""
for l in f:lines("L") do s = s .. l end
assert(s == "\n\nline\nother")
f:close()
io.input(file)
s = ""
for l in io.lines(nil, "L") do s = s .. l end
assert(s == "\n\nline\nother")
io.input():close()
s = ""
for l in io.lines(file, "L") do s = s .. l end
assert(s == "\n\nline\nother")
s = ""
for l in io.lines(file, "l") do s = s .. l end
assert(s == "lineother")
io.output(file); io.write"a = 10 + 34\na = 2*a\na = -a\n":close()
local t = {}
load(io.lines(file, "L"), nil, nil, t)()
assert(t.a == -((10 + 34) * 2))
-- test for multipe arguments in 'lines'
io.output(file); io.write"0123456789\n":close()
for a,b in io.lines(file, 1, 1) do
if a == "\n" then assert(b == nil)
else assert(tonumber(a) == tonumber(b) - 1)
end
end
for a,b,c in io.lines(file, 1, 2, "a") do
assert(a == "0" and b == "12" and c == "3456789\n")
end
for a,b,c in io.lines(file, "a", 0, 1) do
if a == "" then break end
assert(a == "0123456789\n" and b == nil and c == nil)
end
collectgarbage() -- to close file in previous iteration
io.output(file); io.write"00\n10\n20\n30\n40\n":close()
for a, b in io.lines(file, "n", "n") do
if a == 40 then assert(b == nil)
else assert(a == b - 10)
end
end
-- test load x lines
io.output(file);
io.write[[
local y
= X
X =
X *
2 +
X;
X =
X
- y;
]]:close()
_G.X = 1
assert(not load(io.lines(file)))
collectgarbage() -- to close file in previous iteration
load(io.lines(file, "L"))()
assert(_G.X == 2)
load(io.lines(file, 1))()
assert(_G.X == 4)
load(io.lines(file, 3))()
assert(_G.X == 8)
print('+')
local x1 = "string\n\n\\com \"\"''coisas [[estranhas]] ]]'"
io.output(file)
assert(io.write(string.format("x2 = %q\n-- comment without ending EOS", x1)))
io.close()
assert(loadfile(file))()
assert(x1 == x2)
print('+')
assert(os.remove(file))
assert(not os.remove(file))
assert(not os.remove(otherfile))
-- testing loadfile
local function testloadfile (s, expres)
io.output(file)
if s then io.write(s) end
io.close()
local res = assert(loadfile(file))()
assert(os.remove(file))
assert(res == expres)
end
-- loading empty file
testloadfile(nil, nil)
-- loading file with initial comment without end of line
testloadfile("# a non-ending comment", nil)
-- checking Unicode BOM in files
testloadfile("\xEF\xBB\xBF# some comment\nreturn 234", 234)
testloadfile("\xEF\xBB\xBFreturn 239", 239)
testloadfile("\xEF\xBB\xBF", nil) -- empty file with a BOM
-- checking line numbers in files with initial comments
testloadfile("# a comment\nreturn require'debug'.getinfo(1).currentline", 2)
-- loading binary file
io.output(io.open(file, "wb"))
assert(io.write(string.dump(function () return 10, '\0alo\255', 'hi' end)))
io.close()
a, b, c = assert(loadfile(file))()
assert(a == 10 and b == "\0alo\255" and c == "hi")
assert(os.remove(file))
-- bug in 5.2.1
do
io.output(io.open(file, "wb"))
-- save function with no upvalues
assert(io.write(string.dump(function () return 1 end)))
io.close()
f = assert(loadfile(file, "b", {}))
assert(type(f) == "function" and f() == 1)
assert(os.remove(file))
end
-- loading binary file with initial comment
io.output(io.open(file, "wb"))
assert(io.write("#this is a comment for a binary file\0\n",
string.dump(function () return 20, '\0\0\0' end)))
io.close()
a, b, c = assert(loadfile(file))()
assert(a == 20 and b == "\0\0\0" and c == nil)
assert(os.remove(file))
-- 'loadfile' with 'env'
do
local f = io.open(file, 'w')
f:write[[
if (...) then a = 15; return b, c, d
else return _ENV
end
]]
f:close()
local t = {b = 12, c = "xuxu", d = print}
local f = assert(loadfile(file, 't', t))
local b, c, d = f(1)
assert(t.a == 15 and b == 12 and c == t.c and d == print)
assert(f() == t)
f = assert(loadfile(file, 't', nil))
assert(f() == nil)
f = assert(loadfile(file))
assert(f() == _G)
assert(os.remove(file))
end
-- 'loadfile' x modes
do
io.open(file, 'w'):write("return 10"):close()
local s, m = loadfile(file, 'b')
assert(not s and string.find(m, "a text chunk"))
io.open(file, 'w'):write("\27 return 10"):close()
local s, m = loadfile(file, 't')
assert(not s and string.find(m, "a binary chunk"))
assert(os.remove(file))
end
io.output(file)
assert(io.write("qualquer coisa\n"))
assert(io.write("mais qualquer coisa"))
io.close()
assert(io.output(assert(io.open(otherfile, 'wb')))
:write("outra coisa\0\1\3\0\0\0\0\255\0")
:close())
local filehandle = assert(io.open(file, 'r+'))
local otherfilehandle = assert(io.open(otherfile, 'rb'))
assert(filehandle ~= otherfilehandle)
assert(type(filehandle) == "userdata")
assert(filehandle:read('l') == "qualquer coisa")
io.input(otherfilehandle)
assert(io.read(string.len"outra coisa") == "outra coisa")
assert(filehandle:read('l') == "mais qualquer coisa")
filehandle:close();
assert(type(filehandle) == "userdata")
io.input(otherfilehandle)
assert(io.read(4) == "\0\1\3\0")
assert(io.read(3) == "\0\0\0")
assert(io.read(0) == "") -- 255 is not eof
assert(io.read(1) == "\255")
assert(io.read('a') == "\0")
assert(not io.read(0))
assert(otherfilehandle == io.input())
otherfilehandle:close()
assert(os.remove(file))
assert(os.remove(otherfile))
collectgarbage()
io.output(file)
:write[[
123.4 -56e-2 not a number
second line
third line
and the rest of the file
]]
:close()
io.input(file)
local _,a,b,c,d,e,h,__ = io.read(1, 'n', 'n', 'l', 'l', 'l', 'a', 10)
assert(io.close(io.input()))
assert(_ == ' ' and __ == nil)
assert(type(a) == 'number' and a==123.4 and b==-56e-2)
assert(d=='second line' and e=='third line')
assert(h==[[
and the rest of the file
]])
assert(os.remove(file))
collectgarbage()
-- testing buffers
do
local f = assert(io.open(file, "w"))
local fr = assert(io.open(file, "r"))
assert(f:setvbuf("full", 2000))
f:write("x")
assert(fr:read("all") == "") -- full buffer; output not written yet
f:close()
fr:seek("set")
assert(fr:read("all") == "x") -- `close' flushes it
f = assert(io.open(file), "w")
assert(f:setvbuf("no"))
f:write("x")
fr:seek("set")
assert(fr:read("all") == "x") -- no buffer; output is ready
f:close()
f = assert(io.open(file, "a"))
assert(f:setvbuf("line"))
f:write("x")
fr:seek("set", 1)
assert(fr:read("all") == "") -- line buffer; no output without `\n'
f:write("a\n"):seek("set", 1)
assert(fr:read("all") == "xa\n") -- now we have a whole line
f:close(); fr:close()
assert(os.remove(file))
end
if not _soft then
print("testing large files (> BUFSIZ)")
io.output(file)
for i=1,5001 do io.write('0123456789123') end
io.write('\n12346'):close()
io.input(file)
local x = io.read('a')
io.input():seek('set', 0)
local y = io.read(30001)..io.read(1005)..io.read(0)..
io.read(1)..io.read(100003)
assert(x == y and string.len(x) == 5001*13 + 6)
io.input():seek('set', 0)
y = io.read() -- huge line
assert(x == y..'\n'..io.read())
assert(io.read() == nil)
io.close(io.input())
assert(os.remove(file))
x = nil; y = nil
end
if not _port then
local progname
do -- get name of running executable
local arg = arg or _ARG
local i = 0
while arg[i] do i = i - 1 end
progname = '"' .. arg[i + 1] .. '"'
end
print("testing popen/pclose and execute")
local tests = {
-- command, what, code
{"ls > /dev/null", "ok"},
{"not-to-be-found-command", "exit"},
{"exit 3", "exit", 3},
{"exit 129", "exit", 129},
{"kill -s HUP $$", "signal", 1},
{"kill -s KILL $$", "signal", 9},
{"sh -c 'kill -s HUP $$'", "exit"},
{progname .. ' -e " "', "ok"},
{progname .. ' -e "os.exit(0, true)"', "ok"},
{progname .. ' -e "os.exit(20, true)"', "exit", 20},
}
print("\n(some error messages are expected now)")
for _, v in ipairs(tests) do
local x, y, z = io.popen(v[1]):close()
local x1, y1, z1 = os.execute(v[1])
assert(x == x1 and y == y1 and z == z1)
if v[2] == "ok" then
assert(x and y == 'exit' and z == 0)
else
assert(not x and y == v[2]) -- correct status and 'what'
-- correct code if known (but always different from 0)
assert((v[3] == nil and z > 0) or v[3] == z)
end
end
end
-- testing tmpfile
f = io.tmpfile()
assert(io.type(f) == "file")
f:write("alo")
f:seek("set")
assert(f:read"a" == "alo")
end --}
print'+'
print("testing date/time")
assert(os.date("") == "")
assert(os.date("!") == "")
assert(os.date("\0\0") == "\0\0")
assert(os.date("!\0\0") == "\0\0")
local x = string.rep("a", 10000)
assert(os.date(x) == x)
local t = os.time()
D = os.date("*t", t)
assert(os.date(string.rep("%d", 1000), t) ==
string.rep(os.date("%d", t), 1000))
assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
local t = os.time()
D = os.date("*t", t)
load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
D.hour==%H and D.min==%M and D.sec==%S and
D.wday==%w+1 and D.yday==%j and type(D.isdst) == 'boolean')]], t))()
checkerr("invalid conversion specifier", os.date, "%")
checkerr("invalid conversion specifier", os.date, "%9")
checkerr("invalid conversion specifier", os.date, "%")
checkerr("invalid conversion specifier", os.date, "%O")
checkerr("invalid conversion specifier", os.date, "%E")
checkerr("invalid conversion specifier", os.date, "%Ea")
checkerr("not an integer", os.time, {year=1000, month=1, day=1, hour='x'})
checkerr("not an integer", os.time, {year=1000, month=1, day=1, hour=1.5})
checkerr("missing", os.time, {hour = 12}) -- missing date
if not _port then
-- test Posix-specific modifiers
assert(type(os.date("%Ex")) == 'string')
assert(type(os.date("%Oy")) == 'string')
-- test out-of-range dates (at least for Unix)
if maxint >= 2^62 then -- cannot do these tests in Small Lua
-- no arith overflows
checkerr("out-of-bound", os.time, {year = -maxint, month = 1, day = 1})
if string.packsize("i") == 4 then -- 4-byte ints
if testerr("out-of-bound", os.date, "%Y", 2^40) then
-- time_t has 4 bytes and therefore cannot represent year 4000
print(" 4-byte time_t")
checkerr("cannot be represented", os.time, {year=4000, month=1, day=1})
else
-- time_t has 8 bytes; an int year cannot represent a huge time
print(" 8-byte time_t")
checkerr("cannot be represented", os.date, "%Y", 2^60)
-- it should have no problems with year 4000
assert(tonumber(os.time{year=4000, month=1, day=1}))
end
else -- 8-byte ints
-- assume time_t has 8 bytes too
print(" 8-byte time_t")
assert(tonumber(os.date("%Y", 2^60)))
-- but still cannot represent a huge year
checkerr("cannot be represented", os.time, {year=2^60, month=1, day=1})
end
end
end
D = os.date("!*t", t)
load(os.date([[!assert(D.year==%Y and D.month==%m and D.day==%d and
D.hour==%H and D.min==%M and D.sec==%S and
D.wday==%w+1 and D.yday==%j and type(D.isdst) == 'boolean')]], t))()
do
local D = os.date("*t")
local t = os.time(D)
assert(type(D.isdst) == 'boolean')
D.isdst = nil
local t1 = os.time(D)
assert(t == t1) -- if isdst is absent uses correct default
end
t = os.time(D)
D.year = D.year-1;
local t1 = os.time(D)
-- allow for leap years
assert(math.abs(os.difftime(t,t1)/(24*3600) - 365) < 2)
-- should not take more than 1 second to execute these two lines
t = os.time()
t1 = os.time(os.date("*t"))
local diff = os.difftime(t1,t)
assert(0 <= diff and diff <= 1)
diff = os.difftime(t,t1)
assert(-1 <= diff and diff <= 0)
local t1 = os.time{year=2000, month=10, day=1, hour=23, min=12}
local t2 = os.time{year=2000, month=10, day=1, hour=23, min=10, sec=19}
assert(os.difftime(t1,t2) == 60*2-19)
-- since 5.3.3, 'os.time' normalizes table fields
t1 = {year = 2005, month = 1, day = 1, hour = 1, min = 0, sec = -3602}
os.time(t1)
assert(t1.day == 31 and t1.month == 12 and t1.year == 2004 and
t1.hour == 23 and t1.min == 59 and t1.sec == 58 and
t1.yday == 366)
io.output(io.stdout)
local t = os.date('%d %m %Y %H %M %S')
local d, m, a, h, min, s = string.match(t,
"(%d+) (%d+) (%d+) (%d+) (%d+) (%d+)")
d = tonumber(d)
m = tonumber(m)
a = tonumber(a)
h = tonumber(h)
min = tonumber(min)
s = tonumber(s)
io.write(string.format('test done on %2.2d/%2.2d/%d', d, m, a))
io.write(string.format(', at %2.2d:%2.2d:%2.2d\n', h, min, s))
io.write(string.format('%s\n', _VERSION))

@ -0,0 +1,624 @@
-- $Id: gc.lua,v 1.72 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print('testing garbage collection')
local debug = require"debug"
collectgarbage()
assert(collectgarbage("isrunning"))
local function gcinfo () return collectgarbage"count" * 1024 end
-- test weird parameters
do
-- save original parameters
local a = collectgarbage("setpause", 200)
local b = collectgarbage("setstepmul", 200)
local t = {0, 2, 10, 90, 500, 5000, 30000, 0x7ffffffe}
for i = 1, #t do
local p = t[i]
for j = 1, #t do
local m = t[j]
collectgarbage("setpause", p)
collectgarbage("setstepmul", m)
collectgarbage("step", 0)
collectgarbage("step", 10000)
end
end
-- restore original parameters
collectgarbage("setpause", a)
collectgarbage("setstepmul", b)
collectgarbage()
end
_G["while"] = 234
limit = 5000
local function GC1 ()
local u
local b -- must be declared after 'u' (to be above it in the stack)
local finish = false
u = setmetatable({}, {__gc = function () finish = true end})
b = {34}
repeat u = {} until finish
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
finish = false; local i = 1
u = setmetatable({}, {__gc = function () finish = true end})
repeat i = i + 1; u = tostring(i) .. tostring(i) until finish
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
finish = false
u = setmetatable({}, {__gc = function () finish = true end})
repeat local i; u = function () return i end until finish
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
end
local function GC2 ()
local u
local finish = false
u = {setmetatable({}, {__gc = function () finish = true end})}
b = {34}
repeat u = {{}} until finish
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
finish = false; local i = 1
u = {setmetatable({}, {__gc = function () finish = true end})}
repeat i = i + 1; u = {tostring(i) .. tostring(i)} until finish
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
finish = false
u = {setmetatable({}, {__gc = function () finish = true end})}
repeat local i; u = {function () return i end} until finish
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
end
local function GC() GC1(); GC2() end
contCreate = 0
print('tables')
while contCreate <= limit do
local a = {}; a = nil
contCreate = contCreate+1
end
a = "a"
contCreate = 0
print('strings')
while contCreate <= limit do
a = contCreate .. "b";
a = string.gsub(a, '(%d%d*)', string.upper)
a = "a"
contCreate = contCreate+1
end
contCreate = 0
a = {}
print('functions')
function a:test ()
while contCreate <= limit do
load(string.format("function temp(a) return 'a%d' end", contCreate), "")()
assert(temp() == string.format('a%d', contCreate))
contCreate = contCreate+1
end
end
a:test()
-- collection of functions without locals, globals, etc.
do local f = function () end end
print("functions with errors")
prog = [[
do
a = 10;
function foo(x,y)
a = sin(a+0.456-0.23e-12);
return function (z) return sin(%x+z) end
end
local x = function (w) a=a+w; end
end
]]
do
local step = 1
if _soft then step = 13 end
for i=1, string.len(prog), step do
for j=i, string.len(prog), step do
pcall(load(string.sub(prog, i, j), ""))
end
end
end
foo = nil
print('long strings')
x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
assert(string.len(x)==80)
s = ''
n = 0
k = math.min(300, (math.maxinteger // 80) // 2)
while n < k do s = s..x; n=n+1; j=tostring(n) end
assert(string.len(s) == k*80)
s = string.sub(s, 1, 10000)
s, i = string.gsub(s, '(%d%d%d%d)', '')
assert(i==10000 // 4)
s = nil
x = nil
assert(_G["while"] == 234)
print("steps")
print("steps (2)")
local function dosteps (siz)
assert(not collectgarbage("isrunning"))
collectgarbage()
assert(not collectgarbage("isrunning"))
local a = {}
for i=1,100 do a[i] = {{}}; local b = {} end
local x = gcinfo()
local i = 0
repeat -- do steps until it completes a collection cycle
i = i+1
until collectgarbage("step", siz)
assert(gcinfo() < x)
return i
end
collectgarbage"stop"
if not _port then
-- test the "size" of basic GC steps (whatever they mean...)
assert(dosteps(0) > 10)
assert(dosteps(10) < dosteps(2))
end
-- collector should do a full collection with so many steps
assert(dosteps(20000) == 1)
assert(collectgarbage("step", 20000) == true)
assert(collectgarbage("step", 20000) == true)
assert(not collectgarbage("isrunning"))
collectgarbage"restart"
assert(collectgarbage("isrunning"))
if not _port then
-- test the pace of the collector
collectgarbage(); collectgarbage()
local x = gcinfo()
collectgarbage"stop"
assert(not collectgarbage("isrunning"))
repeat
local a = {}
until gcinfo() > 3 * x
collectgarbage"restart"
assert(collectgarbage("isrunning"))
repeat
local a = {}
until gcinfo() <= x * 2
end
print("clearing tables")
lim = 15
a = {}
-- fill a with `collectable' indices
for i=1,lim do a[{}] = i end
b = {}
for k,v in pairs(a) do b[k]=v end
-- remove all indices and collect them
for n in pairs(b) do
a[n] = nil
assert(type(n) == 'table' and next(n) == nil)
collectgarbage()
end
b = nil
collectgarbage()
for n in pairs(a) do error'cannot be here' end
for i=1,lim do a[i] = i end
for i=1,lim do assert(a[i] == i) end
print('weak tables')
a = {}; setmetatable(a, {__mode = 'k'});
-- fill a with some `collectable' indices
for i=1,lim do a[{}] = i end
-- and some non-collectable ones
for i=1,lim do a[i] = i end
for i=1,lim do local s=string.rep('@', i); a[s] = s..'#' end
collectgarbage()
local i = 0
for k,v in pairs(a) do assert(k==v or k..'#'==v); i=i+1 end
assert(i == 2*lim)
a = {}; setmetatable(a, {__mode = 'v'});
a[1] = string.rep('b', 21)
collectgarbage()
assert(a[1]) -- strings are *values*
a[1] = nil
-- fill a with some `collectable' values (in both parts of the table)
for i=1,lim do a[i] = {} end
for i=1,lim do a[i..'x'] = {} end
-- and some non-collectable ones
for i=1,lim do local t={}; a[t]=t end
for i=1,lim do a[i+lim]=i..'x' end
collectgarbage()
local i = 0
for k,v in pairs(a) do assert(k==v or k-lim..'x' == v); i=i+1 end
assert(i == 2*lim)
a = {}; setmetatable(a, {__mode = 'vk'});
local x, y, z = {}, {}, {}
-- keep only some items
a[1], a[2], a[3] = x, y, z
a[string.rep('$', 11)] = string.rep('$', 11)
-- fill a with some `collectable' values
for i=4,lim do a[i] = {} end
for i=1,lim do a[{}] = i end
for i=1,lim do local t={}; a[t]=t end
collectgarbage()
assert(next(a) ~= nil)
local i = 0
for k,v in pairs(a) do
assert((k == 1 and v == x) or
(k == 2 and v == y) or
(k == 3 and v == z) or k==v);
i = i+1
end
assert(i == 4)
x,y,z=nil
collectgarbage()
assert(next(a) == string.rep('$', 11))
-- 'bug' in 5.1
a = {}
local t = {x = 10}
local C = setmetatable({key = t}, {__mode = 'v'})
local C1 = setmetatable({[t] = 1}, {__mode = 'k'})
a.x = t -- this should not prevent 't' from being removed from
-- weak table 'C' by the time 'a' is finalized
setmetatable(a, {__gc = function (u)
assert(C.key == nil)
assert(type(next(C1)) == 'table')
end})
a, t = nil
collectgarbage()
collectgarbage()
assert(next(C) == nil and next(C1) == nil)
C, C1 = nil
-- ephemerons
local mt = {__mode = 'k'}
a = {{10},{20},{30},{40}}; setmetatable(a, mt)
x = nil
for i = 1, 100 do local n = {}; a[n] = {k = {x}}; x = n end
GC()
local n = x
local i = 0
while n do n = a[n].k[1]; i = i + 1 end
assert(i == 100)
x = nil
GC()
for i = 1, 4 do assert(a[i][1] == i * 10); a[i] = nil end
assert(next(a) == nil)
local K = {}
a[K] = {}
for i=1,10 do a[K][i] = {}; a[a[K][i]] = setmetatable({}, mt) end
x = nil
local k = 1
for j = 1,100 do
local n = {}; local nk = k%10 + 1
a[a[K][nk]][n] = {x, k = k}; x = n; k = nk
end
GC()
local n = x
local i = 0
while n do local t = a[a[K][k]][n]; n = t[1]; k = t.k; i = i + 1 end
assert(i == 100)
K = nil
GC()
-- assert(next(a) == nil)
-- testing errors during GC
do
collectgarbage("stop") -- stop collection
local u = {}
local s = {}; setmetatable(s, {__mode = 'k'})
setmetatable(u, {__gc = function (o)
local i = s[o]
s[i] = true
assert(not s[i - 1]) -- check proper finalization order
if i == 8 then error("here") end -- error during GC
end})
for i = 6, 10 do
local n = setmetatable({}, getmetatable(u))
s[n] = i
end
assert(not pcall(collectgarbage))
for i = 8, 10 do assert(s[i]) end
for i = 1, 5 do
local n = setmetatable({}, getmetatable(u))
s[n] = i
end
collectgarbage()
for i = 1, 10 do assert(s[i]) end
getmetatable(u).__gc = false
-- __gc errors with non-string messages
setmetatable({}, {__gc = function () error{} end})
local a, b = pcall(collectgarbage)
assert(not a and type(b) == "string" and string.find(b, "error in __gc"))
end
print '+'
-- testing userdata
if T==nil then
(Message or print)('\n >>> testC not active: skipping userdata GC tests <<<\n')
else
local function newproxy(u)
return debug.setmetatable(T.newuserdata(0), debug.getmetatable(u))
end
collectgarbage("stop") -- stop collection
local u = newproxy(nil)
debug.setmetatable(u, {__gc = true})
local s = 0
local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'})
for i=1,10 do a[newproxy(u)] = i end
for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end
local a1 = {}; for k,v in pairs(a) do a1[k] = v end
for k,v in pairs(a1) do a[v] = k end
for i =1,10 do assert(a[i]) end
getmetatable(u).a = a1
getmetatable(u).u = u
do
local u = u
getmetatable(u).__gc = function (o)
assert(a[o] == 10-s)
assert(a[10-s] == nil) -- udata already removed from weak table
assert(getmetatable(o) == getmetatable(u))
assert(getmetatable(o).a[o] == 10-s)
s=s+1
end
end
a1, u = nil
assert(next(a) ~= nil)
collectgarbage()
assert(s==11)
collectgarbage()
assert(next(a) == nil) -- finalized keys are removed in two cycles
end
-- __gc x weak tables
local u = setmetatable({}, {__gc = true})
-- __gc metamethod should be collected before running
setmetatable(getmetatable(u), {__mode = "v"})
getmetatable(u).__gc = function (o) os.exit(1) end -- cannot happen
u = nil
collectgarbage()
local u = setmetatable({}, {__gc = true})
local m = getmetatable(u)
m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"});
m.__gc = function (o)
assert(next(getmetatable(o).x) == nil)
m = 10
end
u, m = nil
collectgarbage()
assert(m==10)
-- errors during collection
u = setmetatable({}, {__gc = function () error "!!!" end})
u = nil
assert(not pcall(collectgarbage))
if not _soft then
print("deep structures")
local a = {}
for i = 1,200000 do
a = {next = a}
end
collectgarbage()
end
-- create many threads with self-references and open upvalues
print("self-referenced threads")
local thread_id = 0
local threads = {}
local function fn (thread)
local x = {}
threads[thread_id] = function()
thread = x
end
coroutine.yield()
end
while thread_id < 1000 do
local thread = coroutine.create(fn)
coroutine.resume(thread, thread)
thread_id = thread_id + 1
end
-- Create a closure (function inside 'f') with an upvalue ('param') that
-- points (through a table) to the closure itself and to the thread
-- ('co' and the initial value of 'param') where closure is running.
-- Then, assert that table (and therefore everything else) will be
-- collected.
do
local collected = false -- to detect collection
collectgarbage(); collectgarbage("stop")
do
local function f (param)
;(function ()
assert(type(f) == 'function' and type(param) == 'thread')
param = {param, f}
setmetatable(param, {__gc = function () collected = true end})
coroutine.yield(100)
end)()
end
local co = coroutine.create(f)
assert(coroutine.resume(co, co))
end
-- Now, thread and closure are not reacheable any more;
-- two collections are needed to break cycle
collectgarbage()
assert(not collected)
collectgarbage()
assert(collected)
collectgarbage("restart")
end
do
collectgarbage()
collectgarbage"stop"
local x = gcinfo()
repeat
for i=1,1000 do _ENV.a = {} end
collectgarbage("step", 0) -- steps should not unblock the collector
until gcinfo() > 2 * x
collectgarbage"restart"
end
if T then -- tests for weird cases collecting upvalues
local function foo ()
local a = {x = 20}
coroutine.yield(function () return a.x end) -- will run collector
assert(a.x == 20) -- 'a' is 'ok'
a = {x = 30} -- create a new object
assert(T.gccolor(a) == "white") -- of course it is new...
coroutine.yield(100) -- 'a' is still local to this thread
end
local t = setmetatable({}, {__mode = "kv"})
collectgarbage(); collectgarbage('stop')
-- create coroutine in a weak table, so it will never be marked
t.co = coroutine.wrap(foo)
local f = t.co() -- create function to access local 'a'
T.gcstate("atomic") -- ensure all objects are traversed
assert(T.gcstate() == "atomic")
assert(t.co() == 100) -- resume coroutine, creating new table for 'a'
assert(T.gccolor(t.co) == "white") -- thread was not traversed
T.gcstate("pause") -- collect thread, but should mark 'a' before that
assert(t.co == nil and f() == 30) -- ensure correct access to 'a'
collectgarbage("restart")
-- test barrier in sweep phase (advance cleaning of upvalue to white)
local u = T.newuserdata(0) -- create a userdata
collectgarbage()
collectgarbage"stop"
T.gcstate"atomic"
T.gcstate"sweepallgc"
local x = {}
assert(T.gccolor(u) == "black") -- upvalue is "old" (black)
assert(T.gccolor(x) == "white") -- table is "new" (white)
debug.setuservalue(u, x) -- trigger barrier
assert(T.gccolor(u) == "white") -- upvalue changed to white
collectgarbage"restart"
print"+"
end
if T then
local debug = require "debug"
collectgarbage("stop")
local x = T.newuserdata(0)
local y = T.newuserdata(0)
debug.setmetatable(y, {__gc = true}) -- bless the new udata before...
debug.setmetatable(x, {__gc = true}) -- ...the old one
assert(T.gccolor(y) == "white")
T.checkmemory()
collectgarbage("restart")
end
if T then
print("emergency collections")
collectgarbage()
collectgarbage()
T.totalmem(T.totalmem() + 200)
for i=1,200 do local a = {} end
T.totalmem(0)
collectgarbage()
local t = T.totalmem("table")
local a = {{}, {}, {}} -- create 4 new tables
assert(T.totalmem("table") == t + 4)
t = T.totalmem("function")
a = function () end -- create 1 new closure
assert(T.totalmem("function") == t + 1)
t = T.totalmem("thread")
a = coroutine.create(function () end) -- create 1 new coroutine
assert(T.totalmem("thread") == t + 1)
end
-- create an object to be collected when state is closed
do
local setmetatable,assert,type,print,getmetatable =
setmetatable,assert,type,print,getmetatable
local tt = {}
tt.__gc = function (o)
assert(getmetatable(o) == tt)
-- create new objects during GC
local a = 'xuxu'..(10+3)..'joao', {}
___Glob = o -- ressurect object!
setmetatable({}, tt) -- creates a new one with same metatable
print(">>> closing state " .. "<<<\n")
end
local u = setmetatable({}, tt)
___Glob = {u} -- avoid object being collected before program end
end
-- create several objects to raise errors when collected while closing state
do
local mt = {__gc = function (o) return o + 1 end}
for i = 1,10 do
-- create object and preserve it until the end
table.insert(___Glob, setmetatable({}, mt))
end
end
-- just to make sure
assert(collectgarbage'isrunning')
print('OK')

@ -0,0 +1,232 @@
-- $Id: goto.lua,v 1.13 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
collectgarbage()
local function errmsg (code, m)
local st, msg = load(code)
assert(not st and string.find(msg, m))
end
-- cannot see label inside block
errmsg([[ goto l1; do ::l1:: end ]], "label 'l1'")
errmsg([[ do ::l1:: end goto l1; ]], "label 'l1'")
-- repeated label
errmsg([[ ::l1:: ::l1:: ]], "label 'l1'")
-- undefined label
errmsg([[ goto l1; local aa ::l1:: ::l2:: print(3) ]], "local 'aa'")
-- jumping over variable definition
errmsg([[
do local bb, cc; goto l1; end
local aa
::l1:: print(3)
]], "local 'aa'")
-- jumping into a block
errmsg([[ do ::l1:: end goto l1 ]], "label 'l1'")
errmsg([[ goto l1 do ::l1:: end ]], "label 'l1'")
-- cannot continue a repeat-until with variables
errmsg([[
repeat
if x then goto cont end
local xuxu = 10
::cont::
until xuxu < x
]], "local 'xuxu'")
-- simple gotos
local x
do
local y = 12
goto l1
::l2:: x = x + 1; goto l3
::l1:: x = y; goto l2
end
::l3:: ::l3_1:: assert(x == 13)
-- long labels
do
local prog = [[
do
local a = 1
goto l%sa; a = a + 1
::l%sa:: a = a + 10
goto l%sb; a = a + 2
::l%sb:: a = a + 20
return a
end
]]
local label = string.rep("0123456789", 40)
prog = string.format(prog, label, label, label, label)
assert(assert(load(prog))() == 31)
end
-- goto to correct label when nested
do goto l3; ::l3:: end -- does not loop jumping to previous label 'l3'
-- ok to jump over local dec. to end of block
do
goto l1
local a = 23
x = a
::l1::;
end
while true do
goto l4
goto l1 -- ok to jump over local dec. to end of block
goto l1 -- multiple uses of same label
local x = 45
::l1:: ;;;
end
::l4:: assert(x == 13)
if print then
goto l1 -- ok to jump over local dec. to end of block
error("should not be here")
goto l2 -- ok to jump over local dec. to end of block
local x
::l1:: ; ::l2:: ;;
else end
-- to repeat a label in a different function is OK
local function foo ()
local a = {}
goto l3
::l1:: a[#a + 1] = 1; goto l2;
::l2:: a[#a + 1] = 2; goto l5;
::l3::
::l3a:: a[#a + 1] = 3; goto l1;
::l4:: a[#a + 1] = 4; goto l6;
::l5:: a[#a + 1] = 5; goto l4;
::l6:: assert(a[1] == 3 and a[2] == 1 and a[3] == 2 and
a[4] == 5 and a[5] == 4)
if not a[6] then a[6] = true; goto l3a end -- do it twice
end
::l6:: foo()
do -- bug in 5.2 -> 5.3.2
local x
::L1::
local y -- cannot join this SETNIL with previous one
assert(y == nil)
y = true
if x == nil then
x = 1
goto L1
else
x = x + 1
end
assert(x == 2 and y == true)
end
--------------------------------------------------------------------------------
-- testing closing of upvalues
-- local debug = require 'debug'
local function foo ()
local t = {}
do
local i = 1
local a, b, c, d
t[1] = function () return a, b, c, d end
::l1::
local b
do
local c
t[#t + 1] = function () return a, b, c, d end -- t[2], t[4], t[6]
if i > 2 then goto l2 end
do
local d
t[#t + 1] = function () return a, b, c, d end -- t[3], t[5]
i = i + 1
local a
goto l1
end
end
end
::l2:: return t
end
local a = foo()
assert(#a == 6)
-- -- all functions share same 'a'
-- for i = 2, 6 do
-- assert(debug.upvalueid(a[1], 1) == debug.upvalueid(a[i], 1))
-- end
-- -- 'b' and 'c' are shared among some of them
-- for i = 2, 6 do
-- -- only a[1] uses external 'b'/'b'
-- assert(debug.upvalueid(a[1], 2) ~= debug.upvalueid(a[i], 2))
-- assert(debug.upvalueid(a[1], 3) ~= debug.upvalueid(a[i], 3))
-- end
-- for i = 3, 5, 2 do
-- -- inner functions share 'b'/'c' with previous ones
-- assert(debug.upvalueid(a[i], 2) == debug.upvalueid(a[i - 1], 2))
-- assert(debug.upvalueid(a[i], 3) == debug.upvalueid(a[i - 1], 3))
-- -- but not with next ones
-- assert(debug.upvalueid(a[i], 2) ~= debug.upvalueid(a[i + 1], 2))
-- assert(debug.upvalueid(a[i], 3) ~= debug.upvalueid(a[i + 1], 3))
-- end
-- -- only external 'd' is shared
-- for i = 2, 6, 2 do
-- assert(debug.upvalueid(a[1], 4) == debug.upvalueid(a[i], 4))
-- end
-- -- internal 'd's are all different
-- for i = 3, 5, 2 do
-- for j = 1, 6 do
-- assert((debug.upvalueid(a[i], 4) == debug.upvalueid(a[j], 4))
-- == (i == j))
-- end
-- end
--------------------------------------------------------------------------------
-- testing if x goto optimizations
local function testG (a)
if a == 1 then
goto l1
error("should never be here!")
elseif a == 2 then goto l2
elseif a == 3 then goto l3
elseif a == 4 then
goto l1 -- go to inside the block
error("should never be here!")
::l1:: a = a + 1 -- must go to 'if' end
else
goto l4
::l4a:: a = a * 2; goto l4b
error("should never be here!")
::l4:: goto l4a
error("should never be here!")
::l4b::
end
do return a end
::l2:: do return "2" end
::l3:: do return "3" end
::l1:: return "1"
end
assert(testG(1) == "1")
assert(testG(2) == "2")
assert(testG(3) == "3")
assert(testG(4) == 5)
assert(testG(5) == 10)
--------------------------------------------------------------------------------
print'OK'

@ -0,0 +1,72 @@
-- $Id: heavy.lua,v 1.4 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print("creating a string too long")
do
local st, msg = pcall(function ()
local a = "x"
while true do
a = a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
.. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
print(string.format("string with %d bytes", #a))
end
end)
assert(not st and
(string.find(msg, "string length overflow") or
string.find(msg, "not enough memory")))
end
print('+')
local function loadrep (x, what)
local p = 1<<20
local s = string.rep(x, p)
local count = 0
local function f()
count = count + p
if count % (0x80*p) == 0 then
io.stderr:write("(", string.format("0x%x", count), ")")
end
return s
end
local st, msg = load(f, "=big")
print(string.format("\ntotal: 0x%x %s", count, what))
return st, msg
end
print("loading chunk with too many lines")
do
local st, msg = loadrep("\n", "lines")
assert(not st and string.find(msg, "too many lines"))
end
print('+')
print("loading chunk with huge identifier")
do
local st, msg = loadrep("a", "chars")
assert(not st and
(string.find(msg, "lexical element too long") or
string.find(msg, "not enough memory")))
end
print('+')
print("loading chunk with too many instructions")
do
local st, msg = loadrep("a = 10; ", "instructions")
print(st, msg)
end
print('+')
print "OK"

@ -0,0 +1,302 @@
-- $Id: literals.lua,v 1.36 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print('testing scanner')
local debug = require "debug"
local function dostring (x) return assert(load(x), "")() end
dostring("x \v\f = \t\r 'a\0a' \v\f\f")
assert(x == 'a\0a' and string.len(x) == 3)
-- escape sequences
assert('\n\"\'\\' == [[
"'\]])
assert(string.find("\a\b\f\n\r\t\v", "^%c%c%c%c%c%c%c$"))
-- assume ASCII just for tests:
assert("\09912" == 'c12')
assert("\99ab" == 'cab')
assert("\099" == '\99')
assert("\099\n" == 'c\10')
assert('\0\0\0alo' == '\0' .. '\0\0' .. 'alo')
assert(010 .. 020 .. -030 == "1020-30")
-- hexadecimal escapes
assert("\x00\x05\x10\x1f\x3C\xfF\xe8" == "\0\5\16\31\60\255\232")
local function lexstring (x, y, n)
local f = assert(load('return ' .. x ..
', require"debug".getinfo(1).currentline', ''))
local s, l = f()
assert(s == y and l == n)
end
lexstring("'abc\\z \n efg'", "abcefg", 2)
lexstring("'abc\\z \n\n\n'", "abc", 4)
lexstring("'\\z \n\t\f\v\n'", "", 3)
lexstring("[[\nalo\nalo\n\n]]", "alo\nalo\n\n", 5)
lexstring("[[\nalo\ralo\n\n]]", "alo\nalo\n\n", 5)
lexstring("[[\nalo\ralo\r\n]]", "alo\nalo\n", 4)
lexstring("[[\ralo\n\ralo\r\n]]", "alo\nalo\n", 4)
lexstring("[[alo]\n]alo]]", "alo]\n]alo", 2)
assert("abc\z
def\z
ghi\z
" == 'abcdefghi')
-- UTF-8 sequences
assert("\u{0}\u{00000000}\x00\0" == string.char(0, 0, 0, 0))
-- limits for 1-byte sequences
assert("\u{0}\u{7F}" == "\x00\z\x7F")
-- limits for 2-byte sequences
assert("\u{80}\u{7FF}" == "\xC2\x80\z\xDF\xBF")
-- limits for 3-byte sequences
assert("\u{800}\u{FFFF}" == "\xE0\xA0\x80\z\xEF\xBF\xBF")
-- limits for 4-byte sequences
assert("\u{10000}\u{10FFFF}" == "\xF0\x90\x80\x80\z\xF4\x8F\xBF\xBF")
-- Error in escape sequences
local function lexerror (s, err)
local st, msg = load('return ' .. s, '')
if err ~= '<eof>' then err = err .. "'" end
assert(not st and string.find(msg, "near .-" .. err))
end
lexerror([["abc\x"]], [[\x"]])
lexerror([["abc\x]], [[\x]])
lexerror([["\x]], [[\x]])
lexerror([["\x5"]], [[\x5"]])
lexerror([["\x5]], [[\x5]])
lexerror([["\xr"]], [[\xr]])
lexerror([["\xr]], [[\xr]])
lexerror([["\x.]], [[\x.]])
lexerror([["\x8%"]], [[\x8%%]])
lexerror([["\xAG]], [[\xAG]])
lexerror([["\g"]], [[\g]])
lexerror([["\g]], [[\g]])
lexerror([["\."]], [[\%.]])
lexerror([["\999"]], [[\999"]])
lexerror([["xyz\300"]], [[\300"]])
lexerror([[" \256"]], [[\256"]])
-- errors in UTF-8 sequences
lexerror([["abc\u{110000}"]], [[abc\u{110000]]) -- too large
lexerror([["abc\u11r"]], [[abc\u1]]) -- missing '{'
lexerror([["abc\u"]], [[abc\u"]]) -- missing '{'
lexerror([["abc\u{11r"]], [[abc\u{11r]]) -- missing '}'
lexerror([["abc\u{11"]], [[abc\u{11"]]) -- missing '}'
lexerror([["abc\u{11]], [[abc\u{11]]) -- missing '}'
lexerror([["abc\u{r"]], [[abc\u{r]]) -- no digits
-- unfinished strings
lexerror("[=[alo]]", "<eof>")
lexerror("[=[alo]=", "<eof>")
lexerror("[=[alo]", "<eof>")
lexerror("'alo", "<eof>")
lexerror("'alo \\z \n\n", "<eof>")
lexerror("'alo \\z", "<eof>")
lexerror([['alo \98]], "<eof>")
-- valid characters in variable names
for i = 0, 255 do
local s = string.char(i)
assert(not string.find(s, "[a-zA-Z_]") == not load(s .. "=1", ""))
assert(not string.find(s, "[a-zA-Z_0-9]") ==
not load("a" .. s .. "1 = 1", ""))
end
-- long variable names
var1 = string.rep('a', 15000) .. '1'
var2 = string.rep('a', 15000) .. '2'
prog = string.format([[
%s = 5
%s = %s + 1
return function () return %s - %s end
]], var1, var2, var1, var1, var2)
local f = dostring(prog)
assert(_G[var1] == 5 and _G[var2] == 6 and f() == -1)
var1, var2, f = nil
print('+')
-- escapes --
assert("\n\t" == [[
]])
assert([[
$debug]] == "\n $debug")
assert([[ [ ]] ~= [[ ] ]])
-- long strings --
b = "001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789"
assert(string.len(b) == 960)
prog = [=[
print('+')
a1 = [["this is a 'string' with several 'quotes'"]]
a2 = "'quotes'"
assert(string.find(a1, a2) == 34)
print('+')
a1 = [==[temp = [[an arbitrary value]]; ]==]
assert(load(a1))()
assert(temp == 'an arbitrary value')
-- long strings --
b = "001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789"
assert(string.len(b) == 960)
print('+')
a = [[00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
00123456789012345678901234567890123456789123456789012345678901234567890123456789
]]
assert(string.len(a) == 1863)
assert(string.sub(a, 1, 40) == string.sub(b, 1, 40))
x = 1
]=]
print('+')
x = nil
dostring(prog)
assert(x)
prog = nil
a = nil
b = nil
-- testing line ends
prog = [[
a = 1 -- a comment
b = 2
x = [=[
hi
]=]
y = "\
hello\r\n\
"
return require"debug".getinfo(1).currentline
]]
for _, n in pairs{"\n", "\r", "\n\r", "\r\n"} do
local prog, nn = string.gsub(prog, "\n", n)
assert(dostring(prog) == nn)
assert(_G.x == "hi\n" and _G.y == "\nhello\r\n\n")
end
-- testing comments and strings with long brackets
a = [==[]=]==]
assert(a == "]=")
a = [==[[===[[=[]]=][====[]]===]===]==]
assert(a == "[===[[=[]]=][====[]]===]===")
a = [====[[===[[=[]]=][====[]]===]===]====]
assert(a == "[===[[=[]]=][====[]]===]===")
a = [=[]]]]]]]]]=]
assert(a == "]]]]]]]]")
--[===[
x y z [==[ blu foo
]==
]
]=]==]
error error]=]===]
-- generate all strings of four of these chars
local x = {"=", "[", "]", "\n"}
local len = 4
local function gen (c, n)
if n==0 then coroutine.yield(c)
else
for _, a in pairs(x) do
gen(c..a, n-1)
end
end
end
for s in coroutine.wrap(function () gen("", len) end) do
assert(s == load("return [====[\n"..s.."]====]", "")())
end
-- testing decimal point locale
if os.setlocale("pt_BR") or os.setlocale("ptb") then
assert(tonumber("3,4") == 3.4 and tonumber"3.4" == 3.4)
assert(tonumber(" -.4 ") == -0.4)
assert(tonumber(" +0x.41 ") == 0X0.41)
assert(not load("a = (3,4)"))
assert(assert(load("return 3.4"))() == 3.4)
assert(assert(load("return .4,3"))() == .4)
assert(assert(load("return 4."))() == 4.)
assert(assert(load("return 4.+.5"))() == 4.5)
assert(" 0x.1 " + " 0x,1" + "-0X.1\t" == 0x0.1)
assert(tonumber"inf" == nil and tonumber"NAN" == nil)
assert(assert(load(string.format("return %q", 4.51)))() == 4.51)
local a,b = load("return 4.5.")
assert(string.find(b, "'4%.5%.'"))
assert(os.setlocale("C"))
else
(Message or print)(
'\n >>> pt_BR locale not available: skipping decimal point tests <<<\n')
end
-- testing %q x line ends
local s = "a string with \r and \n and \r\n and \n\r"
local c = string.format("return %q", s)
assert(assert(load(c))() == s)
-- testing errors
assert(not load"a = 'non-ending string")
assert(not load"a = 'non-ending string\n'")
assert(not load"a = '\\345'")
assert(not load"a = [=x]")
print('OK')

@ -0,0 +1,162 @@
-- $Id: locals.lua,v 1.37 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print('testing local variables and environments')
local debug = require"debug"
-- bug in 5.1:
local function f(x) x = nil; return x end
assert(f(10) == nil)
local function f() local x; return x end
assert(f(10) == nil)
local function f(x) x = nil; local y; return x, y end
assert(f(10) == nil and select(2, f(20)) == nil)
do
local i = 10
do local i = 100; assert(i==100) end
do local i = 1000; assert(i==1000) end
assert(i == 10)
if i ~= 10 then
local i = 20
else
local i = 30
assert(i == 30)
end
end
f = nil
local f
x = 1
a = nil
load('local a = {}')()
assert(a == nil)
function f (a)
local _1, _2, _3, _4, _5
local _6, _7, _8, _9, _10
local x = 3
local b = a
local c,d = a,b
if (d == b) then
local x = 'q'
x = b
assert(x == 2)
else
assert(nil)
end
assert(x == 3)
local f = 10
end
local b=10
local a; repeat local b; a,b=1,2; assert(a+1==b); until a+b==3
assert(x == 1)
f(2)
assert(type(f) == 'function')
local function getenv (f)
local a,b = debug.getupvalue(f, 1)
assert(a == '_ENV')
return b
end
-- test for global table of loaded chunks
assert(getenv(load"a=3") == _G)
local c = {}; local f = load("a = 3", nil, nil, c)
assert(getenv(f) == c)
assert(c.a == nil)
f()
assert(c.a == 3)
-- old test for limits for special instructions (now just a generic test)
do
local i = 2
local p = 4 -- p == 2^i
repeat
for j=-3,3 do
assert(load(string.format([[local a=%s;
a=a+%s;
assert(a ==2^%s)]], j, p-j, i), '')) ()
assert(load(string.format([[local a=%s;
a=a-%s;
assert(a==-2^%s)]], -j, p-j, i), '')) ()
assert(load(string.format([[local a,b=0,%s;
a=b-%s;
assert(a==-2^%s)]], -j, p-j, i), '')) ()
end
p = 2 * p; i = i + 1
until p <= 0
end
print'+'
if rawget(_G, "querytab") then
-- testing clearing of dead elements from tables
collectgarbage("stop") -- stop GC
local a = {[{}] = 4, [3] = 0, alo = 1,
a1234567890123456789012345678901234567890 = 10}
local t = querytab(a)
for k,_ in pairs(a) do a[k] = nil end
collectgarbage() -- restore GC and collect dead fiels in `a'
for i=0,t-1 do
local k = querytab(a, i)
assert(k == nil or type(k) == 'number' or k == 'alo')
end
end
-- testing lexical environments
assert(_ENV == _G)
do
local dummy
local _ENV = (function (...) return ... end)(_G, dummy) -- {
do local _ENV = {assert=assert}; assert(true) end
mt = {_G = _G}
local foo,x
A = false -- "declare" A
do local _ENV = mt
function foo (x)
A = x
do local _ENV = _G; A = 1000 end
return function (x) return A .. x end
end
end
assert(getenv(foo) == mt)
x = foo('hi'); assert(mt.A == 'hi' and A == 1000)
assert(x('*') == mt.A .. '*')
do local _ENV = {assert=assert, A=10};
do local _ENV = {assert=assert, A=20};
assert(A==20);x=A
end
assert(A==10 and x==20)
end
assert(x==20)
print('OK')
return 5,f
end -- }

@ -0,0 +1,381 @@
# testing special comment on first line
-- $Id: main.lua,v 1.65 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
-- most (all?) tests here assume a reasonable "Unix-like" shell
if _port then return end
-- use only "double quotes" inside shell scripts (better change to
-- run on Windows)
print ("testing stand-alone interpreter")
assert(os.execute()) -- machine has a system command
local arg = arg or _ARG
local prog = os.tmpname()
local otherprog = os.tmpname()
local out = os.tmpname()
local progname
do
local i = 0
while arg[i] do i=i-1 end
progname = arg[i+1]
end
print("progname: "..progname)
local prepfile = function (s, p)
p = p or prog
io.output(p)
io.write(s)
assert(io.close())
end
local function getoutput ()
io.input(out)
local t = io.read("a")
io.input():close()
assert(os.remove(out))
return t
end
local function checkprogout (s)
local t = getoutput()
for line in string.gmatch(s, ".-\n") do
assert(string.find(t, line, 1, true))
end
end
local function checkout (s)
local t = getoutput()
if s ~= t then print(string.format("'%s' - '%s'\n", s, t)) end
assert(s == t)
return t
end
local function RUN (p, ...)
p = string.gsub(p, "lua", '"'..progname..'"', 1)
local s = string.format(p, ...)
assert(os.execute(s))
end
local function NoRun (msg, p, ...)
p = string.gsub(p, "lua", '"'..progname..'"', 1)
local s = string.format(p, ...)
s = string.format("%s 2> %s", s, out) -- will send error to 'out'
assert(not os.execute(s))
assert(string.find(getoutput(), msg, 1, true)) -- check error message
end
RUN('lua -v')
print(string.format("(temporary program file used in these tests: %s)", prog))
-- running stdin as a file
prepfile""
RUN('lua - < %s > %s', prog, out)
checkout("")
prepfile[[
print(
1, a
)
]]
RUN('lua - < %s > %s', prog, out)
checkout("1\tnil\n")
RUN('echo "print(10)\nprint(2)\n" | lua > %s', out)
checkout("10\n2\n")
-- test option '-'
RUN('echo "print(arg[1])" | lua - -h > %s', out)
checkout("-h\n")
-- test environment variables used by Lua
prepfile("print(package.path)")
-- test LUA_PATH
RUN('env LUA_INIT= LUA_PATH=x lua %s > %s', prog, out)
checkout("x\n")
-- test LUA_PATH_version
RUN('env LUA_INIT= LUA_PATH_5_3=y LUA_PATH=x lua %s > %s', prog, out)
checkout("y\n")
-- test LUA_CPATH
prepfile("print(package.cpath)")
RUN('env LUA_INIT= LUA_CPATH=xuxu lua %s > %s', prog, out)
checkout("xuxu\n")
-- test LUA_CPATH_version
RUN('env LUA_INIT= LUA_CPATH_5_3=yacc LUA_CPATH=x lua %s > %s', prog, out)
checkout("yacc\n")
-- test LUA_INIT (and its access to 'arg' table)
prepfile("print(X)")
RUN('env LUA_INIT="X=tonumber(arg[1])" lua %s 3.2 > %s', prog, out)
checkout("3.2\n")
-- test LUA_INIT_version
prepfile("print(X)")
RUN('env LUA_INIT_5_3="X=10" LUA_INIT="X=3" lua %s > %s', prog, out)
checkout("10\n")
-- test LUA_INIT for files
prepfile("x = x or 10; print(x); x = x + 1")
RUN('env LUA_INIT="@%s" lua %s > %s', prog, prog, out)
checkout("10\n11\n")
-- test errors in LUA_INIT
NoRun('LUA_INIT:1: msg', 'env LUA_INIT="error(\'msg\')" lua')
-- test option '-E'
local defaultpath, defaultCpath
do
prepfile("print(package.path, package.cpath)")
RUN('env LUA_INIT="error(10)" LUA_PATH=xxx LUA_CPATH=xxx lua -E %s > %s',
prog, out)
local out = getoutput()
defaultpath = string.match(out, "^(.-)\t")
defaultCpath = string.match(out, "\t(.-)$")
end
-- paths did not changed
assert(not string.find(defaultpath, "xxx") and
string.find(defaultpath, "lua") and
not string.find(defaultCpath, "xxx") and
string.find(defaultCpath, "lua"))
-- test replacement of ';;' to default path
local function convert (p)
prepfile("print(package.path)")
RUN('env LUA_PATH="%s" lua %s > %s', p, prog, out)
local expected = getoutput()
expected = string.sub(expected, 1, -2) -- cut final end of line
assert(string.gsub(p, ";;", ";"..defaultpath..";") == expected)
end
convert(";")
convert(";;")
convert(";;;")
convert(";;;;")
convert(";;;;;")
convert(";;a;;;bc")
-- test -l over multiple libraries
prepfile("print(1); a=2; return {x=15}")
prepfile(("print(a); print(_G['%s'].x)"):format(prog), otherprog)
RUN('env LUA_PATH="?;;" lua -l %s -l%s -lstring -l io %s > %s', prog, otherprog, otherprog, out)
checkout("1\n2\n15\n2\n15\n")
-- test 'arg' table
local a = [[
assert(#arg == 3 and arg[1] == 'a' and
arg[2] == 'b' and arg[3] == 'c')
assert(arg[-1] == '--' and arg[-2] == "-e " and arg[-3] == '%s')
assert(arg[4] == nil and arg[-4] == nil)
local a, b, c = ...
assert(... == 'a' and a == 'a' and b == 'b' and c == 'c')
]]
a = string.format(a, progname)
prepfile(a)
RUN('lua "-e " -- %s a b c', prog) -- "-e " runs an empty command
-- test 'arg' availability in libraries
prepfile"assert(arg)"
prepfile("assert(arg)", otherprog)
RUN('env LUA_PATH="?;;" lua -l%s - < %s', prog, otherprog)
-- test messing up the 'arg' table
RUN('echo "print(...)" | lua -e "arg[1] = 100" - > %s', out)
checkout("100\n")
NoRun("'arg' is not a table", 'echo "" | lua -e "arg = 1" -')
-- test error in 'print'
RUN('echo 10 | lua -e "print=nil" -i > /dev/null 2> %s', out)
assert(string.find(getoutput(), "error calling 'print'"))
-- test 'debug.debug'
RUN('echo "io.stderr:write(1000)\ncont" | lua -e "require\'debug\'.debug()" 2> %s', out)
checkout("lua_debug> 1000lua_debug> ")
-- test many arguments
prepfile[[print(({...})[30])]]
RUN('lua %s %s > %s', prog, string.rep(" a", 30), out)
checkout("a\n")
RUN([[lua "-eprint(1)" -ea=3 -e "print(a)" > %s]], out)
checkout("1\n3\n")
-- test iteractive mode
prepfile[[
(6*2-6) -- ===
a =
10
print(a)
a]]
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
checkprogout("6\n10\n10\n\n")
prepfile("a = [[b\nc\nd\ne]]\n=a")
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
checkprogout("b\nc\nd\ne\n\n")
prompt = "alo"
prepfile[[ --
a = 2
]]
RUN([[lua "-e_PROMPT='%s'" -i < %s > %s]], prompt, prog, out)
local t = getoutput()
assert(string.find(t, prompt .. ".*" .. prompt .. ".*" .. prompt))
-- test for error objects
prepfile[[
debug = require "debug"
m = {x=0}
setmetatable(m, {__tostring = function(x)
return tostring(debug.getinfo(4).currentline + x.x)
end})
error(m)
]]
NoRun(progname .. ": 6\n", [[lua %s]], prog)
prepfile("error{}")
NoRun("error object is a table value", [[lua %s]], prog)
-- chunk broken in many lines
s = [=[ --
function f ( x )
local a = [[
xuxu
]]
local b = "\
xuxu\n"
if x == 11 then return 1 + 12 , 2 + 20 end --[[ test multiple returns ]]
return x + 1
--\\
end
return( f( 100 ) )
assert( a == b )
do return f( 11 ) end ]=]
s = string.gsub(s, ' ', '\n\n') -- change all spaces for newlines
prepfile(s)
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
checkprogout("101\n13\t22\n\n")
prepfile[[#comment in 1st line without \n at the end]]
RUN('lua %s', prog)
prepfile[[#test line number when file starts with comment line
debug = require"debug"
print(debug.getinfo(1).currentline)
]]
RUN('lua %s > %s', prog, out)
checkprogout('3')
-- close Lua with an open file
prepfile(string.format([[io.output(%q); io.write('alo')]], out))
RUN('lua %s', prog)
checkout('alo')
-- bug in 5.2 beta (extra \0 after version line)
RUN([[lua -v -e"print'hello'" > %s]], out)
t = getoutput()
assert(string.find(t, "PUC%-Rio\nhello"))
-- testing os.exit
prepfile("os.exit(nil, true)")
RUN('lua %s', prog)
prepfile("os.exit(0, true)")
RUN('lua %s', prog)
prepfile("os.exit(true, true)")
RUN('lua %s', prog)
prepfile("os.exit(1, true)")
NoRun("", "lua %s", prog) -- no message
prepfile("os.exit(false, true)")
NoRun("", "lua %s", prog) -- no message
-- remove temporary files
assert(os.remove(prog))
assert(os.remove(otherprog))
assert(not os.remove(out))
-- invalid options
NoRun("unrecognized option '-h'", "lua -h")
NoRun("unrecognized option '---'", "lua ---")
NoRun("unrecognized option '-Ex'", "lua -Ex")
NoRun("unrecognized option '-vv'", "lua -vv")
NoRun("unrecognized option '-iv'", "lua -iv")
NoRun("'-e' needs argument", "lua -e")
NoRun("syntax error", "lua -e a")
NoRun("'-l' needs argument", "lua -l")
if T then -- auxiliary library?
print("testing 'not enough memory' to create a state")
NoRun("not enough memory", "env MEMLIMIT=100 lua")
end
print('+')
print('testing Ctrl C')
do
-- interrupt a script
local function kill (pid)
return os.execute(string.format('kill -INT %d 2> /dev/null', pid))
end
-- function to run a script in background, returning its output file
-- descriptor and its pid
local function runback (luaprg)
-- shell script to run 'luaprg' in background and echo its pid
local shellprg = string.format('%s -e "%s" & echo $!', progname, luaprg)
local f = io.popen(shellprg, "r") -- run shell script
local pid = f:read() -- get pid for Lua script
print("(if test fails now, it may leave a Lua script running in \z
background, pid " .. pid .. ")")
return f, pid
end
-- Lua script that runs protected infinite loop and then prints '42'
local f, pid = runback[[
pcall(function () print(12); while true do end end); print(42)]]
-- wait until script is inside 'pcall'
assert(f:read() == "12")
kill(pid) -- send INT signal to Lua script
-- check that 'pcall' captured the exception and script continued running
assert(f:read() == "42") -- expected output
assert(f:close())
print("done")
-- Lua script in a long unbreakable search
local f, pid = runback[[
print(15); string.find(string.rep('a', 100000), '.*b')]]
-- wait (so script can reach the loop)
assert(f:read() == "15")
assert(os.execute("sleep 1"))
-- must send at least two INT signals to stop this Lua script
local n = 100
for i = 0, 100 do -- keep sending signals
if not kill(pid) then -- until it fails
n = i -- number of non-failed kills
break
end
end
assert(f:close())
assert(n >= 2)
print(string.format("done (with %d kills)", n))
end
print("OK")

@ -0,0 +1,824 @@
-- $Id: math.lua,v 1.78 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print("testing numbers and math lib")
local minint = math.mininteger
local maxint = math.maxinteger
local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1
assert((1 << intbits) == 0)
assert(minint == 1 << (intbits - 1))
assert(maxint == minint - 1)
-- number of bits in the mantissa of a floating-point number
local floatbits = 24
do
local p = 2.0^floatbits
while p < p + 1.0 do
p = p * 2.0
floatbits = floatbits + 1
end
end
local function isNaN (x)
return (x ~= x)
end
assert(isNaN(0/0))
assert(not isNaN(1/0))
do
local x = 2.0^floatbits
assert(x > x - 1.0 and x == x + 1.0)
print(string.format("%d-bit integers, %d-bit (mantissa) floats",
intbits, floatbits))
end
assert(math.type(0) == "integer" and math.type(0.0) == "float"
and math.type("10") == nil)
local function checkerror (msg, f, ...)
local s, err = pcall(f, ...)
assert(not s and string.find(err, msg))
end
local msgf2i = "number.* has no integer representation"
-- float equality
function eq (a,b,limit)
if not limit then
if floatbits >= 50 then limit = 1E-11
else limit = 1E-5
end
end
-- a == b needed for +inf/-inf
return a == b or math.abs(a-b) <= limit
end
-- equality with types
function eqT (a,b)
return a == b and math.type(a) == math.type(b)
end
-- basic float notation
assert(0e12 == 0 and .0 == 0 and 0. == 0 and .2e2 == 20 and 2.E-1 == 0.2)
do
local a,b,c = "2", " 3e0 ", " 10 "
assert(a+b == 5 and -b == -3 and b+"2" == 5 and "10"-c == 0)
assert(type(a) == 'string' and type(b) == 'string' and type(c) == 'string')
assert(a == "2" and b == " 3e0 " and c == " 10 " and -c == -" 10 ")
assert(c%a == 0 and a^b == 08)
a = 0
assert(a == -a and 0 == -0)
end
do
local x = -1
local mz = 0/x -- minus zero
t = {[0] = 10, 20, 30, 40, 50}
assert(t[mz] == t[0] and t[-0] == t[0])
end
do -- tests for 'modf'
local a,b = math.modf(3.5)
assert(a == 3.0 and b == 0.5)
a,b = math.modf(-2.5)
assert(a == -2.0 and b == -0.5)
a,b = math.modf(-3e23)
assert(a == -3e23 and b == 0.0)
a,b = math.modf(3e35)
assert(a == 3e35 and b == 0.0)
a,b = math.modf(-1/0) -- -inf
assert(a == -1/0 and b == 0.0)
a,b = math.modf(1/0) -- inf
assert(a == 1/0 and b == 0.0)
a,b = math.modf(0/0) -- NaN
assert(isNaN(a) and isNaN(b))
a,b = math.modf(3) -- integer argument
assert(eqT(a, 3) and eqT(b, 0.0))
a,b = math.modf(minint)
assert(eqT(a, minint) and eqT(b, 0.0))
end
assert(math.huge > 10e30)
assert(-math.huge < -10e30)
-- integer arithmetic
assert(minint < minint + 1)
assert(maxint - 1 < maxint)
assert(0 - minint == minint)
assert(minint * minint == 0)
assert(maxint * maxint * maxint == maxint)
-- testing floor division and conversions
for _, i in pairs{-16, -15, -3, -2, -1, 0, 1, 2, 3, 15} do
for _, j in pairs{-16, -15, -3, -2, -1, 1, 2, 3, 15} do
for _, ti in pairs{0, 0.0} do -- try 'i' as integer and as float
for _, tj in pairs{0, 0.0} do -- try 'j' as integer and as float
local x = i + ti
local y = j + tj
assert(i//j == math.floor(i/j))
end
end
end
end
assert(1//0.0 == 1/0)
assert(-1 // 0.0 == -1/0)
assert(eqT(3.5 // 1.5, 2.0))
assert(eqT(3.5 // -1.5, -3.0))
assert(maxint // maxint == 1)
assert(maxint // 1 == maxint)
assert((maxint - 1) // maxint == 0)
assert(maxint // (maxint - 1) == 1)
assert(minint // minint == 1)
assert(minint // minint == 1)
assert((minint + 1) // minint == 0)
assert(minint // (minint + 1) == 1)
assert(minint // 1 == minint)
assert(minint // -1 == -minint)
assert(minint // -2 == 2^(intbits - 2))
assert(maxint // -1 == -maxint)
-- negative exponents
do
assert(2^-3 == 1 / 2^3)
assert(eq((-3)^-3, 1 / (-3)^3))
for i = -3, 3 do -- variables avoid constant folding
for j = -3, 3 do
-- domain errors (0^(-n)) are not portable
if not _port or i ~= 0 or j > 0 then
assert(eq(i^j, 1 / i^(-j)))
end
end
end
end
-- comparison between floats and integers (border cases)
if floatbits < intbits then
assert(2.0^floatbits == (1 << floatbits))
assert(2.0^floatbits - 1.0 == (1 << floatbits) - 1.0)
assert(2.0^floatbits - 1.0 ~= (1 << floatbits))
-- float is rounded, int is not
assert(2.0^floatbits + 1.0 ~= (1 << floatbits) + 1)
else -- floats can express all integers with full accuracy
assert(maxint == maxint + 0.0)
assert(maxint - 1 == maxint - 1.0)
assert(minint + 1 == minint + 1.0)
assert(maxint ~= maxint - 1.0)
end
assert(maxint + 0.0 == 2.0^(intbits - 1) - 1.0)
assert(minint + 0.0 == minint)
assert(minint + 0.0 == -2.0^(intbits - 1))
-- order between floats and integers
assert(1 < 1.1); assert(not (1 < 0.9))
assert(1 <= 1.1); assert(not (1 <= 0.9))
assert(-1 < -0.9); assert(not (-1 < -1.1))
assert(1 <= 1.1); assert(not (-1 <= -1.1))
assert(-1 < -0.9); assert(not (-1 < -1.1))
assert(-1 <= -0.9); assert(not (-1 <= -1.1))
assert(minint <= minint + 0.0)
assert(minint + 0.0 <= minint)
assert(not (minint < minint + 0.0))
assert(not (minint + 0.0 < minint))
assert(maxint < minint * -1.0)
assert(maxint <= minint * -1.0)
do
local fmaxi1 = 2^(intbits - 1)
assert(maxint < fmaxi1)
assert(maxint <= fmaxi1)
assert(not (fmaxi1 <= maxint))
assert(minint <= -2^(intbits - 1))
assert(-2^(intbits - 1) <= minint)
end
if floatbits < intbits then
print("testing order (floats cannot represent all integers)")
local fmax = 2^floatbits
local ifmax = fmax | 0
assert(fmax < ifmax + 1)
assert(fmax - 1 < ifmax)
assert(-(fmax - 1) > -ifmax)
assert(not (fmax <= ifmax - 1))
assert(-fmax > -(ifmax + 1))
assert(not (-fmax >= -(ifmax - 1)))
assert(fmax/2 - 0.5 < ifmax//2)
assert(-(fmax/2 - 0.5) > -ifmax//2)
assert(maxint < 2^intbits)
assert(minint > -2^intbits)
assert(maxint <= 2^intbits)
assert(minint >= -2^intbits)
else
print("testing order (floats can represent all integers)")
assert(maxint < maxint + 1.0)
assert(maxint < maxint + 0.5)
assert(maxint - 1.0 < maxint)
assert(maxint - 0.5 < maxint)
assert(not (maxint + 0.0 < maxint))
assert(maxint + 0.0 <= maxint)
assert(not (maxint < maxint + 0.0))
assert(maxint + 0.0 <= maxint)
assert(maxint <= maxint + 0.0)
assert(not (maxint + 1.0 <= maxint))
assert(not (maxint + 0.5 <= maxint))
assert(not (maxint <= maxint - 1.0))
assert(not (maxint <= maxint - 0.5))
assert(minint < minint + 1.0)
assert(minint < minint + 0.5)
assert(minint <= minint + 0.5)
assert(minint - 1.0 < minint)
assert(minint - 1.0 <= minint)
assert(not (minint + 0.0 < minint))
assert(not (minint + 0.5 < minint))
assert(not (minint < minint + 0.0))
assert(minint + 0.0 <= minint)
assert(minint <= minint + 0.0)
assert(not (minint + 1.0 <= minint))
assert(not (minint + 0.5 <= minint))
assert(not (minint <= minint - 1.0))
end
do
local NaN = 0/0
assert(not (NaN < 0))
assert(not (NaN > minint))
assert(not (NaN <= -9))
assert(not (NaN <= maxint))
assert(not (NaN < maxint))
assert(not (minint <= NaN))
assert(not (minint < NaN))
end
-- avoiding errors at compile time
local function checkcompt (msg, code)
checkerror(msg, assert(load(code)))
end
checkcompt("divide by zero", "return 2 // 0")
checkcompt(msgf2i, "return 2.3 >> 0")
checkcompt(msgf2i, ("return 2.0^%d & 1"):format(intbits - 1))
checkcompt("field 'huge'", "return math.huge << 1")
checkcompt(msgf2i, ("return 1 | 2.0^%d"):format(intbits - 1))
checkcompt(msgf2i, "return 2.3 ~ '0.0'")
-- testing overflow errors when converting from float to integer (runtime)
local function f2i (x) return x | x end
checkerror(msgf2i, f2i, math.huge) -- +inf
checkerror(msgf2i, f2i, -math.huge) -- -inf
checkerror(msgf2i, f2i, 0/0) -- NaN
if floatbits < intbits then
-- conversion tests when float cannot represent all integers
assert(maxint + 1.0 == maxint + 0.0)
assert(minint - 1.0 == minint + 0.0)
checkerror(msgf2i, f2i, maxint + 0.0)
assert(f2i(2.0^(intbits - 2)) == 1 << (intbits - 2))
assert(f2i(-2.0^(intbits - 2)) == -(1 << (intbits - 2)))
assert((2.0^(floatbits - 1) + 1.0) // 1 == (1 << (floatbits - 1)) + 1)
-- maximum integer representable as a float
local mf = maxint - (1 << (floatbits - intbits)) + 1
assert(f2i(mf + 0.0) == mf) -- OK up to here
mf = mf + 1
assert(f2i(mf + 0.0) ~= mf) -- no more representable
else
-- conversion tests when float can represent all integers
assert(maxint + 1.0 > maxint)
assert(minint - 1.0 < minint)
assert(f2i(maxint + 0.0) == maxint)
checkerror("no integer rep", f2i, maxint + 1.0)
checkerror("no integer rep", f2i, minint - 1.0)
end
-- 'minint' should be representable as a float no matter the precision
assert(f2i(minint + 0.0) == minint)
-- testing numeric strings
assert("2" + 1 == 3)
assert("2 " + 1 == 3)
assert(" -2 " + 1 == -1)
assert(" -0xa " + 1 == -9)
-- Literal integer Overflows (new behavior in 5.3.3)
do
-- no overflows
assert(eqT(tonumber(tostring(maxint)), maxint))
assert(eqT(tonumber(tostring(minint)), minint))
-- add 1 to last digit as a string (it cannot be 9...)
local function incd (n)
local s = string.format("%d", n)
s = string.gsub(s, "%d$", function (d)
assert(d ~= '9')
return string.char(string.byte(d) + 1)
end)
return s
end
-- 'tonumber' with overflow by 1
assert(eqT(tonumber(incd(maxint)), maxint + 1.0))
assert(eqT(tonumber(incd(minint)), minint - 1.0))
-- large numbers
assert(eqT(tonumber("1"..string.rep("0", 30)), 1e30))
assert(eqT(tonumber("-1"..string.rep("0", 30)), -1e30))
-- hexa format still wraps around
assert(eqT(tonumber("0x1"..string.rep("0", 30)), 0))
-- lexer in the limits
assert(minint == load("return " .. minint)())
assert(eqT(maxint, load("return " .. maxint)()))
assert(eqT(10000000000000000000000.0, 10000000000000000000000))
assert(eqT(-10000000000000000000000.0, -10000000000000000000000))
end
-- testing 'tonumber'
-- 'tonumber' with numbers
assert(tonumber(3.4) == 3.4)
assert(eqT(tonumber(3), 3))
assert(eqT(tonumber(maxint), maxint) and eqT(tonumber(minint), minint))
assert(tonumber(1/0) == 1/0)
-- 'tonumber' with strings
assert(tonumber("0") == 0)
assert(tonumber("") == nil)
assert(tonumber(" ") == nil)
assert(tonumber("-") == nil)
assert(tonumber(" -0x ") == nil)
assert(tonumber{} == nil)
assert(tonumber'+0.01' == 1/100 and tonumber'+.01' == 0.01 and
tonumber'.01' == 0.01 and tonumber'-1.' == -1 and
tonumber'+1.' == 1)
assert(tonumber'+ 0.01' == nil and tonumber'+.e1' == nil and
tonumber'1e' == nil and tonumber'1.0e+' == nil and
tonumber'.' == nil)
assert(tonumber('-012') == -010-2)
assert(tonumber('-1.2e2') == - - -120)
assert(tonumber("0xffffffffffff") == (1 << (4*12)) - 1)
assert(tonumber("0x"..string.rep("f", (intbits//4))) == -1)
assert(tonumber("-0x"..string.rep("f", (intbits//4))) == 1)
-- testing 'tonumber' with base
assert(tonumber(' 001010 ', 2) == 10)
assert(tonumber(' 001010 ', 10) == 001010)
assert(tonumber(' -1010 ', 2) == -10)
assert(tonumber('10', 36) == 36)
assert(tonumber(' -10 ', 36) == -36)
assert(tonumber(' +1Z ', 36) == 36 + 35)
assert(tonumber(' -1z ', 36) == -36 + -35)
assert(tonumber('-fFfa', 16) == -(10+(16*(15+(16*(15+(16*15)))))))
assert(tonumber(string.rep('1', (intbits - 2)), 2) + 1 == 2^(intbits - 2))
assert(tonumber('ffffFFFF', 16)+1 == (1 << 32))
assert(tonumber('0ffffFFFF', 16)+1 == (1 << 32))
assert(tonumber('-0ffffffFFFF', 16) - 1 == -(1 << 40))
for i = 2,36 do
local i2 = i * i
local i10 = i2 * i2 * i2 * i2 * i2 -- i^10
assert(tonumber('\t10000000000\t', i) == i10)
end
if not _soft then
-- tests with very long numerals
assert(tonumber("0x"..string.rep("f", 13)..".0") == 2.0^(4*13) - 1)
assert(tonumber("0x"..string.rep("f", 150)..".0") == 2.0^(4*150) - 1)
assert(tonumber("0x"..string.rep("f", 300)..".0") == 2.0^(4*300) - 1)
assert(tonumber("0x"..string.rep("f", 500)..".0") == 2.0^(4*500) - 1)
assert(tonumber('0x3.' .. string.rep('0', 1000)) == 3)
assert(tonumber('0x' .. string.rep('0', 1000) .. 'a') == 10)
assert(tonumber('0x0.' .. string.rep('0', 13).."1") == 2.0^(-4*14))
assert(tonumber('0x0.' .. string.rep('0', 150).."1") == 2.0^(-4*151))
assert(tonumber('0x0.' .. string.rep('0', 300).."1") == 2.0^(-4*301))
assert(tonumber('0x0.' .. string.rep('0', 500).."1") == 2.0^(-4*501))
assert(tonumber('0xe03' .. string.rep('0', 1000) .. 'p-4000') == 3587.0)
assert(tonumber('0x.' .. string.rep('0', 1000) .. '74p4004') == 0x7.4)
end
-- testing 'tonumber' for invalid formats
local function f (...)
if select('#', ...) == 1 then
return (...)
else
return "***"
end
end
assert(f(tonumber('fFfa', 15)) == nil)
assert(f(tonumber('099', 8)) == nil)
assert(f(tonumber('1\0', 2)) == nil)
assert(f(tonumber('', 8)) == nil)
assert(f(tonumber(' ', 9)) == nil)
assert(f(tonumber(' ', 9)) == nil)
assert(f(tonumber('0xf', 10)) == nil)
assert(f(tonumber('inf')) == nil)
assert(f(tonumber(' INF ')) == nil)
assert(f(tonumber('Nan')) == nil)
assert(f(tonumber('nan')) == nil)
assert(f(tonumber(' ')) == nil)
assert(f(tonumber('')) == nil)
assert(f(tonumber('1 a')) == nil)
assert(f(tonumber('1 a', 2)) == nil)
assert(f(tonumber('1\0')) == nil)
assert(f(tonumber('1 \0')) == nil)
assert(f(tonumber('1\0 ')) == nil)
assert(f(tonumber('e1')) == nil)
assert(f(tonumber('e 1')) == nil)
assert(f(tonumber(' 3.4.5 ')) == nil)
-- testing 'tonumber' for invalid hexadecimal formats
assert(tonumber('0x') == nil)
assert(tonumber('x') == nil)
assert(tonumber('x3') == nil)
assert(tonumber('0x3.3.3') == nil) -- two decimal points
assert(tonumber('00x2') == nil)
assert(tonumber('0x 2') == nil)
assert(tonumber('0 x2') == nil)
assert(tonumber('23x') == nil)
assert(tonumber('- 0xaa') == nil)
assert(tonumber('-0xaaP ') == nil) -- no exponent
assert(tonumber('0x0.51p') == nil)
assert(tonumber('0x5p+-2') == nil)
-- testing hexadecimal numerals
assert(0x10 == 16 and 0xfff == 2^12 - 1 and 0XFB == 251)
assert(0x0p12 == 0 and 0x.0p-3 == 0)
assert(0xFFFFFFFF == (1 << 32) - 1)
assert(tonumber('+0x2') == 2)
assert(tonumber('-0xaA') == -170)
assert(tonumber('-0xffFFFfff') == -(1 << 32) + 1)
-- possible confusion with decimal exponent
assert(0E+1 == 0 and 0xE+1 == 15 and 0xe-1 == 13)
-- floating hexas
assert(tonumber(' 0x2.5 ') == 0x25/16)
assert(tonumber(' -0x2.5 ') == -0x25/16)
assert(tonumber(' +0x0.51p+8 ') == 0x51)
assert(0x.FfffFFFF == 1 - '0x.00000001')
assert('0xA.a' + 0 == 10 + 10/16)
assert(0xa.aP4 == 0XAA)
assert(0x4P-2 == 1)
assert(0x1.1 == '0x1.' + '+0x.1')
assert(0Xabcdef.0 == 0x.ABCDEFp+24)
assert(1.1 == 1.+.1)
assert(100.0 == 1E2 and .01 == 1e-2)
assert(1111111111 - 1111111110 == 1000.00e-03)
assert(1.1 == '1.'+'.1')
assert(tonumber'1111111111' - tonumber'1111111110' ==
tonumber" +0.001e+3 \n\t")
assert(0.1e-30 > 0.9E-31 and 0.9E30 < 0.1e31)
assert(0.123456 > 0.123455)
assert(tonumber('+1.23E18') == 1.23*10.0^18)
-- testing order operators
assert(not(1<1) and (1<2) and not(2<1))
assert(not('a'<'a') and ('a'<'b') and not('b'<'a'))
assert((1<=1) and (1<=2) and not(2<=1))
assert(('a'<='a') and ('a'<='b') and not('b'<='a'))
assert(not(1>1) and not(1>2) and (2>1))
assert(not('a'>'a') and not('a'>'b') and ('b'>'a'))
assert((1>=1) and not(1>=2) and (2>=1))
assert(('a'>='a') and not('a'>='b') and ('b'>='a'))
assert(1.3 < 1.4 and 1.3 <= 1.4 and not (1.3 < 1.3) and 1.3 <= 1.3)
-- testing mod operator
assert(eqT(-4 % 3, 2))
assert(eqT(4 % -3, -2))
assert(eqT(-4.0 % 3, 2.0))
assert(eqT(4 % -3.0, -2.0))
assert(math.pi - math.pi % 1 == 3)
assert(math.pi - math.pi % 0.001 == 3.141)
assert(eqT(minint % minint, 0))
assert(eqT(maxint % maxint, 0))
assert((minint + 1) % minint == minint + 1)
assert((maxint - 1) % maxint == maxint - 1)
assert(minint % maxint == maxint - 1)
assert(minint % -1 == 0)
assert(minint % -2 == 0)
assert(maxint % -2 == -1)
-- non-portable tests because Windows C library cannot compute
-- fmod(1, huge) correctly
if not _port then
local function anan (x) assert(isNaN(x)) end -- assert Not a Number
anan(0.0 % 0)
anan(1.3 % 0)
anan(math.huge % 1)
anan(math.huge % 1e30)
anan(-math.huge % 1e30)
anan(-math.huge % -1e30)
assert(1 % math.huge == 1)
assert(1e30 % math.huge == 1e30)
assert(1e30 % -math.huge == -math.huge)
assert(-1 % math.huge == math.huge)
assert(-1 % -math.huge == -1)
end
-- testing unsigned comparisons
assert(math.ult(3, 4))
assert(not math.ult(4, 4))
assert(math.ult(-2, -1))
assert(math.ult(2, -1))
assert(not math.ult(-2, -2))
assert(math.ult(maxint, minint))
assert(not math.ult(minint, maxint))
assert(eq(math.sin(-9.8)^2 + math.cos(-9.8)^2, 1))
assert(eq(math.tan(math.pi/4), 1))
assert(eq(math.sin(math.pi/2), 1) and eq(math.cos(math.pi/2), 0))
assert(eq(math.atan(1), math.pi/4) and eq(math.acos(0), math.pi/2) and
eq(math.asin(1), math.pi/2))
assert(eq(math.deg(math.pi/2), 90) and eq(math.rad(90), math.pi/2))
assert(math.abs(-10.43) == 10.43)
assert(eqT(math.abs(minint), minint))
assert(eqT(math.abs(maxint), maxint))
assert(eqT(math.abs(-maxint), maxint))
assert(eq(math.atan(1,0), math.pi/2))
assert(math.fmod(10,3) == 1)
assert(eq(math.sqrt(10)^2, 10))
assert(eq(math.log(2, 10), math.log(2)/math.log(10)))
assert(eq(math.log(2, 2), 1))
assert(eq(math.log(9, 3), 2))
assert(eq(math.exp(0), 1))
assert(eq(math.sin(10), math.sin(10%(2*math.pi))))
assert(tonumber(' 1.3e-2 ') == 1.3e-2)
assert(tonumber(' -1.00000000000001 ') == -1.00000000000001)
-- testing constant limits
-- 2^23 = 8388608
assert(8388609 + -8388609 == 0)
assert(8388608 + -8388608 == 0)
assert(8388607 + -8388607 == 0)
do -- testing floor & ceil
assert(eqT(math.floor(3.4), 3))
assert(eqT(math.ceil(3.4), 4))
assert(eqT(math.floor(-3.4), -4))
assert(eqT(math.ceil(-3.4), -3))
assert(eqT(math.floor(maxint), maxint))
assert(eqT(math.ceil(maxint), maxint))
assert(eqT(math.floor(minint), minint))
assert(eqT(math.floor(minint + 0.0), minint))
assert(eqT(math.ceil(minint), minint))
assert(eqT(math.ceil(minint + 0.0), minint))
assert(math.floor(1e50) == 1e50)
assert(math.ceil(1e50) == 1e50)
assert(math.floor(-1e50) == -1e50)
assert(math.ceil(-1e50) == -1e50)
for _, p in pairs{31,32,63,64} do
assert(math.floor(2^p) == 2^p)
assert(math.floor(2^p + 0.5) == 2^p)
assert(math.ceil(2^p) == 2^p)
assert(math.ceil(2^p - 0.5) == 2^p)
end
checkerror("number expected", math.floor, {})
checkerror("number expected", math.ceil, print)
assert(eqT(math.tointeger(minint), minint))
assert(eqT(math.tointeger(minint .. ""), minint))
assert(eqT(math.tointeger(maxint), maxint))
assert(eqT(math.tointeger(maxint .. ""), maxint))
assert(eqT(math.tointeger(minint + 0.0), minint))
assert(math.tointeger(0.0 - minint) == nil)
assert(math.tointeger(math.pi) == nil)
assert(math.tointeger(-math.pi) == nil)
assert(math.floor(math.huge) == math.huge)
assert(math.ceil(math.huge) == math.huge)
assert(math.tointeger(math.huge) == nil)
assert(math.floor(-math.huge) == -math.huge)
assert(math.ceil(-math.huge) == -math.huge)
assert(math.tointeger(-math.huge) == nil)
assert(math.tointeger("34.0") == 34)
assert(math.tointeger("34.3") == nil)
assert(math.tointeger({}) == nil)
assert(math.tointeger(0/0) == nil) -- NaN
end
-- testing fmod for integers
for i = -6, 6 do
for j = -6, 6 do
if j ~= 0 then
local mi = math.fmod(i, j)
local mf = math.fmod(i + 0.0, j)
assert(mi == mf)
assert(math.type(mi) == 'integer' and math.type(mf) == 'float')
if (i >= 0 and j >= 0) or (i <= 0 and j <= 0) or mi == 0 then
assert(eqT(mi, i % j))
end
end
end
end
assert(eqT(math.fmod(minint, minint), 0))
assert(eqT(math.fmod(maxint, maxint), 0))
assert(eqT(math.fmod(minint + 1, minint), minint + 1))
assert(eqT(math.fmod(maxint - 1, maxint), maxint - 1))
checkerror("zero", math.fmod, 3, 0)
do -- testing max/min
checkerror("value expected", math.max)
checkerror("value expected", math.min)
assert(eqT(math.max(3), 3))
assert(eqT(math.max(3, 5, 9, 1), 9))
assert(math.max(maxint, 10e60) == 10e60)
assert(eqT(math.max(minint, minint + 1), minint + 1))
assert(eqT(math.min(3), 3))
assert(eqT(math.min(3, 5, 9, 1), 1))
assert(math.min(3.2, 5.9, -9.2, 1.1) == -9.2)
assert(math.min(1.9, 1.7, 1.72) == 1.7)
assert(math.min(-10e60, minint) == -10e60)
assert(eqT(math.min(maxint, maxint - 1), maxint - 1))
assert(eqT(math.min(maxint - 2, maxint, maxint - 1), maxint - 2))
end
-- testing implicit convertions
local a,b = '10', '20'
assert(a*b == 200 and a+b == 30 and a-b == -10 and a/b == 0.5 and -b == -20)
assert(a == '10' and b == '20')
do
print("testing -0 and NaN")
local mz, z = -0.0, 0.0
assert(mz == z)
assert(1/mz < 0 and 0 < 1/z)
local a = {[mz] = 1}
assert(a[z] == 1 and a[mz] == 1)
a[z] = 2
assert(a[z] == 2 and a[mz] == 2)
local inf = math.huge * 2 + 1
mz, z = -1/inf, 1/inf
assert(mz == z)
assert(1/mz < 0 and 0 < 1/z)
local NaN = inf - inf
assert(NaN ~= NaN)
assert(not (NaN < NaN))
assert(not (NaN <= NaN))
assert(not (NaN > NaN))
assert(not (NaN >= NaN))
assert(not (0 < NaN) and not (NaN < 0))
local NaN1 = 0/0
assert(NaN ~= NaN1 and not (NaN <= NaN1) and not (NaN1 <= NaN))
local a = {}
assert(not pcall(rawset, a, NaN, 1))
assert(a[NaN] == nil)
a[1] = 1
assert(not pcall(rawset, a, NaN, 1))
assert(a[NaN] == nil)
-- strings with same binary representation as 0.0 (might create problems
-- for constant manipulation in the pre-compiler)
local a1, a2, a3, a4, a5 = 0, 0, "\0\0\0\0\0\0\0\0", 0, "\0\0\0\0\0\0\0\0"
assert(a1 == a2 and a2 == a4 and a1 ~= a3)
assert(a3 == a5)
end
print("testing 'math.random'")
math.randomseed(0)
do -- test random for floats
local max = -math.huge
local min = math.huge
for i = 0, 20000 do
local t = math.random()
assert(0 <= t and t < 1)
max = math.max(max, t)
min = math.min(min, t)
if eq(max, 1, 0.001) and eq(min, 0, 0.001) then
goto ok
end
end
-- loop ended without satisfing condition
assert(false)
::ok::
end
do
local function aux (p, lim) -- test random for small intervals
local x1, x2
if #p == 1 then x1 = 1; x2 = p[1]
else x1 = p[1]; x2 = p[2]
end
local mark = {}; local count = 0 -- to check that all values appeared
for i = 0, lim or 2000 do
local t = math.random(table.unpack(p))
assert(x1 <= t and t <= x2)
if not mark[t] then -- new value
mark[t] = true
count = count + 1
end
if count == x2 - x1 + 1 then -- all values appeared; OK
goto ok
end
end
-- loop ended without satisfing condition
assert(false)
::ok::
end
aux({-10,0})
aux({6})
aux({-10, 10})
aux({minint, minint})
aux({maxint, maxint})
aux({minint, minint + 9})
aux({maxint - 3, maxint})
end
do
local function aux(p1, p2) -- test random for large intervals
local max = minint
local min = maxint
local n = 200
local mark = {}; local count = 0 -- to count how many different values
for _ = 1, n do
local t = math.random(p1, p2)
max = math.max(max, t)
min = math.min(min, t)
if not mark[t] then -- new value
mark[t] = true
count = count + 1
end
end
-- at least 80% of values are different
assert(count >= n * 0.8)
-- min and max not too far from formal min and max
local diff = (p2 - p1) // 8
assert(min < p1 + diff and max > p2 - diff)
end
aux(0, maxint)
aux(1, maxint)
aux(minint, -1)
aux(minint // 2, maxint // 2)
end
for i=1,100 do
assert(math.random(maxint) > 0)
assert(math.random(minint, -1) < 0)
end
assert(not pcall(math.random, 1, 2, 3)) -- too many arguments
-- empty interval
assert(not pcall(math.random, minint + 1, minint))
assert(not pcall(math.random, maxint, maxint - 1))
assert(not pcall(math.random, maxint, minint))
-- interval too large
assert(not pcall(math.random, minint, 0))
assert(not pcall(math.random, -1, maxint))
assert(not pcall(math.random, minint // 2, maxint // 2 + 1))
print('OK')

@ -0,0 +1,631 @@
-- $Id: nextvar.lua,v 1.79 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print('testing tables, next, and for')
local function checkerror (msg, f, ...)
local s, err = pcall(f, ...)
assert(not s and string.find(err, msg))
end
local a = {}
-- make sure table has lots of space in hash part
for i=1,100 do a[i.."+"] = true end
for i=1,100 do a[i.."+"] = nil end
-- fill hash part with numeric indices testing size operator
for i=1,100 do
a[i] = true
assert(#a == i)
end
-- testing ipairs
local x = 0
for k,v in ipairs{10,20,30;x=12} do
x = x + 1
assert(k == x and v == x * 10)
end
for _ in ipairs{x=12, y=24} do assert(nil) end
-- test for 'false' x ipair
x = false
local i = 0
for k,v in ipairs{true,false,true,false} do
i = i + 1
x = not x
assert(x == v)
end
assert(i == 4)
-- iterator function is always the same
assert(type(ipairs{}) == 'function' and ipairs{} == ipairs{})
if not T then
(Message or print)
('\n >>> testC not active: skipping tests for table sizes <<<\n')
else --[
-- testing table sizes
local function log2 (x) return math.log(x, 2) end
local function mp2 (n) -- minimum power of 2 >= n
local mp = 2^math.ceil(log2(n))
assert(n == 0 or (mp/2 < n and n <= mp))
return mp
end
local function fb (n)
local r, nn = T.int2fb(n)
assert(r < 256)
return nn
end
-- test fb function
for a = 1, 10000 do -- all numbers up to 10^4
local n = fb(a)
assert(a <= n and n <= a*1.125)
end
local a = 1024 -- plus a few up to 2 ^30
local lim = 2^30
while a < lim do
local n = fb(a)
assert(a <= n and n <= a*1.125)
a = math.ceil(a*1.3)
end
local function check (t, na, nh)
local a, h = T.querytab(t)
if a ~= na or h ~= nh then
print(na, nh, a, h)
assert(nil)
end
end
-- testing C library sizes
do
local s = 0
for _ in pairs(math) do s = s + 1 end
check(math, 0, mp2(s))
end
-- testing constructor sizes
local lim = 40
local s = 'return {'
for i=1,lim do
s = s..i..','
local s = s
for k=0,lim do
local t = load(s..'}', '')()
assert(#t == i)
check(t, fb(i), mp2(k))
s = string.format('%sa%d=%d,', s, k, k)
end
end
-- tests with unknown number of elements
local a = {}
for i=1,lim do a[i] = i end -- build auxiliary table
for k=0,lim do
local a = {table.unpack(a,1,k)}
assert(#a == k)
check(a, k, 0)
a = {1,2,3,table.unpack(a,1,k)}
check(a, k+3, 0)
assert(#a == k + 3)
end
-- testing tables dynamically built
local lim = 130
local a = {}; a[2] = 1; check(a, 0, 1)
a = {}; a[0] = 1; check(a, 0, 1); a[2] = 1; check(a, 0, 2)
a = {}; a[0] = 1; a[1] = 1; check(a, 1, 1)
a = {}
for i = 1,lim do
a[i] = 1
assert(#a == i)
check(a, mp2(i), 0)
end
a = {}
for i = 1,lim do
a['a'..i] = 1
assert(#a == 0)
check(a, 0, mp2(i))
end
a = {}
for i=1,16 do a[i] = i end
check(a, 16, 0)
do
for i=1,11 do a[i] = nil end
for i=30,50 do a[i] = nil end -- force a rehash (?)
check(a, 0, 8) -- only 5 elements in the table
a[10] = 1
for i=30,50 do a[i] = nil end -- force a rehash (?)
check(a, 0, 8) -- only 6 elements in the table
for i=1,14 do a[i] = nil end
for i=18,50 do a[i] = nil end -- force a rehash (?)
check(a, 0, 4) -- only 2 elements ([15] and [16])
end
-- reverse filling
for i=1,lim do
local a = {}
for i=i,1,-1 do a[i] = i end -- fill in reverse
check(a, mp2(i), 0)
end
-- size tests for vararg
lim = 35
function foo (n, ...)
local arg = {...}
check(arg, n, 0)
assert(select('#', ...) == n)
arg[n+1] = true
check(arg, mp2(n+1), 0)
arg.x = true
check(arg, mp2(n+1), 1)
end
local a = {}
for i=1,lim do a[i] = true; foo(i, table.unpack(a)) end
end --]
-- test size operation on empty tables
assert(#{} == 0)
assert(#{nil} == 0)
assert(#{nil, nil} == 0)
assert(#{nil, nil, nil} == 0)
assert(#{nil, nil, nil, nil} == 0)
print'+'
local nofind = {}
a,b,c = 1,2,3
a,b,c = nil
-- next uses always the same iteraction function
assert(next{} == next{})
local function find (name)
local n,v
while 1 do
n,v = next(_G, n)
if not n then return nofind end
assert(v ~= nil)
if n == name then return v end
end
end
local function find1 (name)
for n,v in pairs(_G) do
if n==name then return v end
end
return nil -- not found
end
assert(print==find("print") and print == find1("print"))
assert(_G["print"]==find("print"))
assert(assert==find1("assert"))
assert(nofind==find("return"))
assert(not find1("return"))
_G["ret" .. "urn"] = nil
assert(nofind==find("return"))
_G["xxx"] = 1
assert(xxx==find("xxx"))
-- invalid key to 'next'
checkerror("invalid key", next, {10,20}, 3)
-- both 'pairs' and 'ipairs' need an argument
checkerror("bad argument", pairs)
checkerror("bad argument", ipairs)
print('+')
a = {}
for i=0,10000 do
if math.fmod(i,10) ~= 0 then
a['x'..i] = i
end
end
n = {n=0}
for i,v in pairs(a) do
n.n = n.n+1
assert(i and v and a[i] == v)
end
assert(n.n == 9000)
a = nil
do -- clear global table
local a = {}
for n,v in pairs(_G) do a[n]=v end
for n,v in pairs(a) do
if not package.loaded[n] and type(v) ~= "function" and
not string.find(n, "^[%u_]") then
_G[n] = nil
end
collectgarbage()
end
end
--
local function checknext (a)
local b = {}
do local k,v = next(a); while k do b[k] = v; k,v = next(a,k) end end
for k,v in pairs(b) do assert(a[k] == v) end
for k,v in pairs(a) do assert(b[k] == v) end
end
checknext{1,x=1,y=2,z=3}
checknext{1,2,x=1,y=2,z=3}
checknext{1,2,3,x=1,y=2,z=3}
checknext{1,2,3,4,x=1,y=2,z=3}
checknext{1,2,3,4,5,x=1,y=2,z=3}
assert(#{} == 0)
assert(#{[-1] = 2} == 0)
assert(#{1,2,3,nil,nil} == 3)
for i=0,40 do
local a = {}
for j=1,i do a[j]=j end
assert(#a == i)
end
-- 'maxn' is now deprecated, but it is easily defined in Lua
function table.maxn (t)
local max = 0
for k in pairs(t) do
max = (type(k) == 'number') and math.max(max, k) or max
end
return max
end
assert(table.maxn{} == 0)
assert(table.maxn{["1000"] = true} == 0)
assert(table.maxn{["1000"] = true, [24.5] = 3} == 24.5)
assert(table.maxn{[1000] = true} == 1000)
assert(table.maxn{[10] = true, [100*math.pi] = print} == 100*math.pi)
table.maxn = nil
-- int overflow
a = {}
for i=0,50 do a[2^i] = true end
assert(a[#a])
print('+')
-- erasing values
local t = {[{1}] = 1, [{2}] = 2, [string.rep("x ", 4)] = 3,
[100.3] = 4, [4] = 5}
local n = 0
for k, v in pairs( t ) do
n = n+1
assert(t[k] == v)
t[k] = nil
collectgarbage()
assert(t[k] == nil)
end
assert(n == 5)
local function test (a)
assert(not pcall(table.insert, a, 2, 20));
table.insert(a, 10); table.insert(a, 2, 20);
table.insert(a, 1, -1); table.insert(a, 40);
table.insert(a, #a+1, 50)
table.insert(a, 2, -2)
assert(not pcall(table.insert, a, 0, 20));
assert(not pcall(table.insert, a, #a + 2, 20));
assert(table.remove(a,1) == -1)
assert(table.remove(a,1) == -2)
assert(table.remove(a,1) == 10)
assert(table.remove(a,1) == 20)
assert(table.remove(a,1) == 40)
assert(table.remove(a,1) == 50)
assert(table.remove(a,1) == nil)
assert(table.remove(a) == nil)
assert(table.remove(a, #a) == nil)
end
a = {n=0, [-7] = "ban"}
test(a)
assert(a.n == 0 and a[-7] == "ban")
a = {[-7] = "ban"};
test(a)
assert(a.n == nil and #a == 0 and a[-7] == "ban")
a = {[-1] = "ban"}
test(a)
assert(#a == 0 and table.remove(a) == nil and a[-1] == "ban")
a = {[0] = "ban"}
assert(#a == 0 and table.remove(a) == "ban" and a[0] == nil)
table.insert(a, 1, 10); table.insert(a, 1, 20); table.insert(a, 1, -1)
assert(table.remove(a) == 10)
assert(table.remove(a) == 20)
assert(table.remove(a) == -1)
assert(table.remove(a) == nil)
a = {'c', 'd'}
table.insert(a, 3, 'a')
table.insert(a, 'b')
assert(table.remove(a, 1) == 'c')
assert(table.remove(a, 1) == 'd')
assert(table.remove(a, 1) == 'a')
assert(table.remove(a, 1) == 'b')
assert(table.remove(a, 1) == nil)
assert(#a == 0 and a.n == nil)
a = {10,20,30,40}
assert(table.remove(a, #a + 1) == nil)
assert(not pcall(table.remove, a, 0))
assert(a[#a] == 40)
assert(table.remove(a, #a) == 40)
assert(a[#a] == 30)
assert(table.remove(a, 2) == 20)
assert(a[#a] == 30 and #a == 2)
do -- testing table library with metamethods
local function test (proxy, t)
for i = 1, 10 do
table.insert(proxy, 1, i)
end
assert(#proxy == 10 and #t == 10)
for i = 1, 10 do
assert(t[i] == 11 - i)
end
table.sort(proxy)
for i = 1, 10 do
assert(t[i] == i and proxy[i] == i)
end
assert(table.concat(proxy, ",") == "1,2,3,4,5,6,7,8,9,10")
for i = 1, 8 do
assert(table.remove(proxy, 1) == i)
end
assert(#proxy == 2 and #t == 2)
local a, b, c = table.unpack(proxy)
assert(a == 9 and b == 10 and c == nil)
end
-- all virtual
local t = {}
local proxy = setmetatable({}, {
__len = function () return #t end,
__index = t,
__newindex = t,
})
test(proxy, t)
-- only __newindex
local count = 0
t = setmetatable({}, {
__newindex = function (t,k,v) count = count + 1; rawset(t,k,v) end})
test(t, t)
assert(count == 10) -- after first 10, all other sets are not new
-- no __newindex
t = setmetatable({}, {
__index = function (_,k) return k + 1 end,
__len = function (_) return 5 end})
assert(table.concat(t, ";") == "2;3;4;5;6")
end
if not T then
(Message or print)
('\n >>> testC not active: skipping tests for table library on non-tables <<<\n')
else --[
local debug = require'debug'
local tab = {10, 20, 30}
local mt = {}
local u = T.newuserdata(0)
checkerror("table expected", table.insert, u, 40)
checkerror("table expected", table.remove, u)
debug.setmetatable(u, mt)
checkerror("table expected", table.insert, u, 40)
checkerror("table expected", table.remove, u)
mt.__index = tab
checkerror("table expected", table.insert, u, 40)
checkerror("table expected", table.remove, u)
mt.__newindex = tab
checkerror("table expected", table.insert, u, 40)
checkerror("table expected", table.remove, u)
mt.__len = function () return #tab end
table.insert(u, 40)
assert(#u == 4 and #tab == 4 and u[4] == 40 and tab[4] == 40)
assert(table.remove(u) == 40)
table.insert(u, 1, 50)
assert(#u == 4 and #tab == 4 and u[4] == 30 and tab[1] == 50)
mt.__newindex = nil
mt.__len = nil
local tab2 = {}
local u2 = T.newuserdata(0)
debug.setmetatable(u2, {__newindex = function (_, k, v) tab2[k] = v end})
table.move(u, 1, 4, 1, u2)
assert(#tab2 == 4 and tab2[1] == tab[1] and tab2[4] == tab[4])
end -- ]
print('+')
a = {}
for i=1,1000 do
a[i] = i; a[i-1] = nil
end
assert(next(a,nil) == 1000 and next(a,1000) == nil)
assert(next({}) == nil)
assert(next({}, nil) == nil)
for a,b in pairs{} do error"not here" end
for i=1,0 do error'not here' end
for i=0,1,-1 do error'not here' end
a = nil; for i=1,1 do assert(not a); a=1 end; assert(a)
a = nil; for i=1,1,-1 do assert(not a); a=1 end; assert(a)
do
print("testing floats in numeric for")
local a
-- integer count
a = 0; for i=1, 1, 1 do a=a+1 end; assert(a==1)
a = 0; for i=10000, 1e4, -1 do a=a+1 end; assert(a==1)
a = 0; for i=1, 0.99999, 1 do a=a+1 end; assert(a==0)
a = 0; for i=9999, 1e4, -1 do a=a+1 end; assert(a==0)
a = 0; for i=1, 0.99999, -1 do a=a+1 end; assert(a==1)
-- float count
a = 0; for i=0, 0.999999999, 0.1 do a=a+1 end; assert(a==10)
a = 0; for i=1.0, 1, 1 do a=a+1 end; assert(a==1)
a = 0; for i=-1.5, -1.5, 1 do a=a+1 end; assert(a==1)
a = 0; for i=1e6, 1e6, -1 do a=a+1 end; assert(a==1)
a = 0; for i=1.0, 0.99999, 1 do a=a+1 end; assert(a==0)
a = 0; for i=99999, 1e5, -1.0 do a=a+1 end; assert(a==0)
a = 0; for i=1.0, 0.99999, -1 do a=a+1 end; assert(a==1)
end
-- conversion
a = 0; for i="10","1","-2" do a=a+1 end; assert(a==5)
do -- checking types
local c
local function checkfloat (i)
assert(math.type(i) == "float")
c = c + 1
end
c = 0; for i = 1.0, 10 do checkfloat(i) end
assert(c == 10)
c = 0; for i = -1, -10, -1.0 do checkfloat(i) end
assert(c == 10)
local function checkint (i)
assert(math.type(i) == "integer")
c = c + 1
end
local m = math.maxinteger
c = 0; for i = m, m - 10, -1 do checkint(i) end
assert(c == 11)
c = 0; for i = 1, 10.9 do checkint(i) end
assert(c == 10)
c = 0; for i = 10, 0.001, -1 do checkint(i) end
assert(c == 10)
c = 0; for i = 1, "10.8" do checkint(i) end
assert(c == 10)
c = 0; for i = 9, "3.4", -1 do checkint(i) end
assert(c == 6)
c = 0; for i = 0, " -3.4 ", -1 do checkint(i) end
assert(c == 4)
c = 0; for i = 100, "96.3", -2 do checkint(i) end
assert(c == 2)
c = 0; for i = 1, math.huge do if i > 10 then break end; checkint(i) end
assert(c == 10)
c = 0; for i = -1, -math.huge, -1 do
if i < -10 then break end; checkint(i)
end
assert(c == 10)
for i = math.mininteger, -10e100 do assert(false) end
for i = math.maxinteger, 10e100, -1 do assert(false) end
end
collectgarbage()
-- testing generic 'for'
local function f (n, p)
local t = {}; for i=1,p do t[i] = i*10 end
return function (_,n)
if n > 0 then
n = n-1
return n, table.unpack(t)
end
end, nil, n
end
local x = 0
for n,a,b,c,d in f(5,3) do
x = x+1
assert(a == 10 and b == 20 and c == 30 and d == nil)
end
assert(x == 5)
-- testing __pairs and __ipairs metamethod
a = {}
do
local x,y,z = pairs(a)
assert(type(x) == 'function' and y == a and z == nil)
end
local function foo (e,i)
assert(e == a)
if i <= 10 then return i+1, i+2 end
end
local function foo1 (e,i)
i = i + 1
assert(e == a)
if i <= e.n then return i,a[i] end
end
setmetatable(a, {__pairs = function (x) return foo, x, 0 end})
local i = 0
for k,v in pairs(a) do
i = i + 1
assert(k == i and v == k+1)
end
a.n = 5
a[3] = 30
-- testing ipairs with metamethods
a = {n=10}
setmetatable(a, { __index = function (t,k)
if k <= t.n then return k * 10 end
end})
i = 0
for k,v in ipairs(a) do
i = i + 1
assert(k == i and v == i * 10)
end
assert(i == a.n)
print"OK"

@ -0,0 +1,374 @@
-- $Id: pm.lua,v 1.48 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print('testing pattern matching')
local function checkerror (msg, f, ...)
local s, err = pcall(f, ...)
assert(not s and string.find(err, msg))
end
function f(s, p)
local i,e = string.find(s, p)
if i then return string.sub(s, i, e) end
end
a,b = string.find('', '') -- empty patterns are tricky
assert(a == 1 and b == 0);
a,b = string.find('alo', '')
assert(a == 1 and b == 0)
a,b = string.find('a\0o a\0o a\0o', 'a', 1) -- first position
assert(a == 1 and b == 1)
a,b = string.find('a\0o a\0o a\0o', 'a\0o', 2) -- starts in the midle
assert(a == 5 and b == 7)
a,b = string.find('a\0o a\0o a\0o', 'a\0o', 9) -- starts in the midle
assert(a == 9 and b == 11)
a,b = string.find('a\0a\0a\0a\0\0ab', '\0ab', 2); -- finds at the end
assert(a == 9 and b == 11);
a,b = string.find('a\0a\0a\0a\0\0ab', 'b') -- last position
assert(a == 11 and b == 11)
assert(string.find('a\0a\0a\0a\0\0ab', 'b\0') == nil) -- check ending
assert(string.find('', '\0') == nil)
assert(string.find('alo123alo', '12') == 4)
assert(string.find('alo123alo', '^12') == nil)
assert(string.match("aaab", ".*b") == "aaab")
assert(string.match("aaa", ".*a") == "aaa")
assert(string.match("b", ".*b") == "b")
assert(string.match("aaab", ".+b") == "aaab")
assert(string.match("aaa", ".+a") == "aaa")
assert(not string.match("b", ".+b"))
assert(string.match("aaab", ".?b") == "ab")
assert(string.match("aaa", ".?a") == "aa")
assert(string.match("b", ".?b") == "b")
assert(f('aloALO', '%l*') == 'alo')
assert(f('aLo_ALO', '%a*') == 'aLo')
assert(f(" \n\r*&\n\r xuxu \n\n", "%g%g%g+") == "xuxu")
assert(f('aaab', 'a*') == 'aaa');
assert(f('aaa', '^.*$') == 'aaa');
assert(f('aaa', 'b*') == '');
assert(f('aaa', 'ab*a') == 'aa')
assert(f('aba', 'ab*a') == 'aba')
assert(f('aaab', 'a+') == 'aaa')
assert(f('aaa', '^.+$') == 'aaa')
assert(f('aaa', 'b+') == nil)
assert(f('aaa', 'ab+a') == nil)
assert(f('aba', 'ab+a') == 'aba')
assert(f('a$a', '.$') == 'a')
assert(f('a$a', '.%$') == 'a$')
assert(f('a$a', '.$.') == 'a$a')
assert(f('a$a', '$$') == nil)
assert(f('a$b', 'a$') == nil)
assert(f('a$a', '$') == '')
assert(f('', 'b*') == '')
assert(f('aaa', 'bb*') == nil)
assert(f('aaab', 'a-') == '')
assert(f('aaa', '^.-$') == 'aaa')
assert(f('aabaaabaaabaaaba', 'b.*b') == 'baaabaaabaaab')
assert(f('aabaaabaaabaaaba', 'b.-b') == 'baaab')
assert(f('alo xo', '.o$') == 'xo')
assert(f(' \n isto é assim', '%S%S*') == 'isto')
assert(f(' \n isto é assim', '%S*$') == 'assim')
assert(f(' \n isto é assim', '[a-z]*$') == 'assim')
assert(f('um caracter ? extra', '[^%sa-z]') == '?')
assert(f('', 'a?') == '')
assert(f('á', 'á?') == 'á')
assert(f('ábl', 'á?b?l?') == 'ábl')
assert(f(' ábl', 'á?b?l?') == '')
assert(f('aa', '^aa?a?a') == 'aa')
assert(f(']]]áb', '[^]]') == 'á')
assert(f("0alo alo", "%x*") == "0a")
assert(f("alo alo", "%C+") == "alo alo")
print('+')
function f1(s, p)
p = string.gsub(p, "%%([0-9])", function (s)
return "%" .. (tonumber(s)+1)
end)
p = string.gsub(p, "^(^?)", "%1()", 1)
p = string.gsub(p, "($?)$", "()%1", 1)
local t = {string.match(s, p)}
return string.sub(s, t[1], t[#t] - 1)
end
assert(f1('alo alx 123 b\0o b\0o', '(..*) %1') == "b\0o b\0o")
assert(f1('axz123= 4= 4 34', '(.+)=(.*)=%2 %1') == '3= 4= 4 3')
assert(f1('=======', '^(=*)=%1$') == '=======')
assert(string.match('==========', '^([=]*)=%1$') == nil)
local function range (i, j)
if i <= j then
return i, range(i+1, j)
end
end
local abc = string.char(range(0, 255));
assert(string.len(abc) == 256)
function strset (p)
local res = {s=''}
string.gsub(abc, p, function (c) res.s = res.s .. c end)
return res.s
end;
assert(string.len(strset('[\200-\210]')) == 11)
assert(strset('[a-z]') == "abcdefghijklmnopqrstuvwxyz")
assert(strset('[a-z%d]') == strset('[%da-uu-z]'))
assert(strset('[a-]') == "-a")
assert(strset('[^%W]') == strset('[%w]'))
assert(strset('[]%%]') == '%]')
assert(strset('[a%-z]') == '-az')
assert(strset('[%^%[%-a%]%-b]') == '-[]^ab')
assert(strset('%Z') == strset('[\1-\255]'))
assert(strset('.') == strset('[\1-\255%z]'))
print('+');
assert(string.match("alo xyzK", "(%w+)K") == "xyz")
assert(string.match("254 K", "(%d*)K") == "")
assert(string.match("alo ", "(%w*)$") == "")
assert(string.match("alo ", "(%w+)$") == nil)
assert(string.find("(álo)", "%(á") == 1)
local a, b, c, d, e = string.match("âlo alo", "^(((.).).* (%w*))$")
assert(a == 'âlo alo' and b == 'âl' and c == 'â' and d == 'alo' and e == nil)
a, b, c, d = string.match('0123456789', '(.+(.?)())')
assert(a == '0123456789' and b == '' and c == 11 and d == nil)
print('+')
assert(string.gsub('ülo ülo', 'ü', 'x') == 'xlo xlo')
assert(string.gsub('alo úlo ', ' +$', '') == 'alo úlo') -- trim
assert(string.gsub(' alo alo ', '^%s*(.-)%s*$', '%1') == 'alo alo') -- double trim
assert(string.gsub('alo alo \n 123\n ', '%s+', ' ') == 'alo alo 123 ')
t = "abç d"
a, b = string.gsub(t, '(.)', '%1@')
assert('@'..a == string.gsub(t, '', '@') and b == 5)
a, b = string.gsub('abçd', '(.)', '%0@', 2)
assert(a == 'a@b@çd' and b == 2)
assert(string.gsub('alo alo', '()[al]', '%1') == '12o 56o')
assert(string.gsub("abc=xyz", "(%w*)(%p)(%w+)", "%3%2%1-%0") ==
"xyz=abc-abc=xyz")
assert(string.gsub("abc", "%w", "%1%0") == "aabbcc")
assert(string.gsub("abc", "%w+", "%0%1") == "abcabc")
assert(string.gsub('áéí', '$', '\0óú') == 'áéí\0óú')
assert(string.gsub('', '^', 'r') == 'r')
assert(string.gsub('', '$', 'r') == 'r')
print('+')
do -- new (5.3.3) semantics for empty matches
assert(string.gsub("a b cd", " *", "-") == "-a-b-c-d-")
local res = ""
local sub = "a \nbc\t\td"
local i = 1
for p, e in string.gmatch(sub, "()%s*()") do
res = res .. string.sub(sub, i, p - 1) .. "-"
i = e
end
assert(res == "-a-b-c-d-")
end
assert(string.gsub("um (dois) tres (quatro)", "(%(%w+%))", string.upper) ==
"um (DOIS) tres (QUATRO)")
do
local function setglobal (n,v) rawset(_G, n, v) end
string.gsub("a=roberto,roberto=a", "(%w+)=(%w%w*)", setglobal)
assert(_G.a=="roberto" and _G.roberto=="a")
end
function f(a,b) return string.gsub(a,'.',b) end
assert(string.gsub("trocar tudo em |teste|b| é |beleza|al|", "|([^|]*)|([^|]*)|", f) ==
"trocar tudo em bbbbb é alalalalalal")
local function dostring (s) return load(s, "")() or "" end
assert(string.gsub("alo $a='x'$ novamente $return a$",
"$([^$]*)%$",
dostring) == "alo novamente x")
x = string.gsub("$x=string.gsub('alo', '.', string.upper)$ assim vai para $return x$",
"$([^$]*)%$", dostring)
assert(x == ' assim vai para ALO')
t = {}
s = 'a alo jose joao'
r = string.gsub(s, '()(%w+)()', function (a,w,b)
assert(string.len(w) == b-a);
t[a] = b-a;
end)
assert(s == r and t[1] == 1 and t[3] == 3 and t[7] == 4 and t[13] == 4)
function isbalanced (s)
return string.find(string.gsub(s, "%b()", ""), "[()]") == nil
end
assert(isbalanced("(9 ((8))(\0) 7) \0\0 a b ()(c)() a"))
assert(not isbalanced("(9 ((8) 7) a b (\0 c) a"))
assert(string.gsub("alo 'oi' alo", "%b''", '"') == 'alo " alo')
local t = {"apple", "orange", "lime"; n=0}
assert(string.gsub("x and x and x", "x", function () t.n=t.n+1; return t[t.n] end)
== "apple and orange and lime")
t = {n=0}
string.gsub("first second word", "%w%w*", function (w) t.n=t.n+1; t[t.n] = w end)
assert(t[1] == "first" and t[2] == "second" and t[3] == "word" and t.n == 3)
t = {n=0}
assert(string.gsub("first second word", "%w+",
function (w) t.n=t.n+1; t[t.n] = w end, 2) == "first second word")
assert(t[1] == "first" and t[2] == "second" and t[3] == nil)
checkerror("invalid replacement value %(a table%)",
string.gsub, "alo", ".", {a = {}})
checkerror("invalid capture index %%2", string.gsub, "alo", ".", "%2")
checkerror("invalid capture index %%0", string.gsub, "alo", "(%0)", "a")
checkerror("invalid capture index %%1", string.gsub, "alo", "(%1)", "a")
checkerror("invalid use of '%%'", string.gsub, "alo", ".", "%x")
-- bug since 2.5 (C-stack overflow)
do
local function f (size)
local s = string.rep("a", size)
local p = string.rep(".?", size)
return pcall(string.match, s, p)
end
local r, m = f(80)
assert(r and #m == 80)
r, m = f(200000)
assert(not r and string.find(m, "too complex"))
end
if not _soft then
print("big strings")
local a = string.rep('a', 300000)
assert(string.find(a, '^a*.?$'))
assert(not string.find(a, '^a*.?b$'))
assert(string.find(a, '^a-.?$'))
-- bug in 5.1.2
a = string.rep('a', 10000) .. string.rep('b', 10000)
assert(not pcall(string.gsub, a, 'b'))
end
-- recursive nest of gsubs
function rev (s)
return string.gsub(s, "(.)(.+)", function (c,s1) return rev(s1)..c end)
end
local x = "abcdef"
assert(rev(rev(x)) == x)
-- gsub with tables
assert(string.gsub("alo alo", ".", {}) == "alo alo")
assert(string.gsub("alo alo", "(.)", {a="AA", l=""}) == "AAo AAo")
assert(string.gsub("alo alo", "(.).", {a="AA", l="K"}) == "AAo AAo")
assert(string.gsub("alo alo", "((.)(.?))", {al="AA", o=false}) == "AAo AAo")
assert(string.gsub("alo alo", "().", {'x','yy','zzz'}) == "xyyzzz alo")
t = {}; setmetatable(t, {__index = function (t,s) return string.upper(s) end})
assert(string.gsub("a alo b hi", "%w%w+", t) == "a ALO b HI")
-- tests for gmatch
local a = 0
for i in string.gmatch('abcde', '()') do assert(i == a+1); a=i end
assert(a==6)
t = {n=0}
for w in string.gmatch("first second word", "%w+") do
t.n=t.n+1; t[t.n] = w
end
assert(t[1] == "first" and t[2] == "second" and t[3] == "word")
t = {3, 6, 9}
for i in string.gmatch ("xuxx uu ppar r", "()(.)%2") do
assert(i == table.remove(t, 1))
end
assert(#t == 0)
t = {}
for i,j in string.gmatch("13 14 10 = 11, 15= 16, 22=23", "(%d+)%s*=%s*(%d+)") do
t[tonumber(i)] = tonumber(j)
end
a = 0
for k,v in pairs(t) do assert(k+1 == v+0); a=a+1 end
assert(a == 3)
-- tests for `%f' (`frontiers')
assert(string.gsub("aaa aa a aaa a", "%f[%w]a", "x") == "xaa xa x xaa x")
assert(string.gsub("[[]] [][] [[[[", "%f[[].", "x") == "x[]] x]x] x[[[")
assert(string.gsub("01abc45de3", "%f[%d]", ".") == ".01abc.45de.3")
assert(string.gsub("01abc45 de3x", "%f[%D]%w", ".") == "01.bc45 de3.")
assert(string.gsub("function", "%f[\1-\255]%w", ".") == ".unction")
assert(string.gsub("function", "%f[^\1-\255]", ".") == "function.")
assert(string.find("a", "%f[a]") == 1)
assert(string.find("a", "%f[^%z]") == 1)
assert(string.find("a", "%f[^%l]") == 2)
assert(string.find("aba", "%f[a%z]") == 3)
assert(string.find("aba", "%f[%z]") == 4)
assert(not string.find("aba", "%f[%l%z]"))
assert(not string.find("aba", "%f[^%l%z]"))
local i, e = string.find(" alo aalo allo", "%f[%S].-%f[%s].-%f[%S]")
assert(i == 2 and e == 5)
local k = string.match(" alo aalo allo", "%f[%S](.-%f[%s].-%f[%S])")
assert(k == 'alo ')
local a = {1, 5, 9, 14, 17,}
for k in string.gmatch("alo alo th02 is 1hat", "()%f[%w%d]") do
assert(table.remove(a, 1) == k)
end
assert(#a == 0)
-- malformed patterns
local function malform (p, m)
m = m or "malformed"
local r, msg = pcall(string.find, "a", p)
assert(not r and string.find(msg, m))
end
malform("(.", "unfinished capture")
malform(".)", "invalid pattern capture")
malform("[a")
malform("[]")
malform("[^]")
malform("[a%]")
malform("[a%")
malform("%b")
malform("%ba")
malform("%")
malform("%f", "missing")
-- \0 in patterns
assert(string.match("ab\0\1\2c", "[\0-\2]+") == "\0\1\2")
assert(string.match("ab\0\1\2c", "[\0-\0]+") == "\0")
assert(string.find("b$a", "$\0?") == 2)
assert(string.find("abc\0efg", "%\0") == 4)
assert(string.match("abc\0efg\0\1e\1g", "%b\0\1") == "\0efg\0\1e\1")
assert(string.match("abc\0\0\0", "%\0+") == "\0\0\0")
assert(string.match("abc\0\0\0", "%\0%\0?") == "\0\0")
-- magic char after \0
assert(string.find("abc\0\0","\0.") == 4)
assert(string.find("abcx\0\0abc\0abc","x\0\0abc\0a.") == 4)
print('OK')

@ -0,0 +1,310 @@
-- $Id: sort.lua,v 1.38 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print "testing (parts of) table library"
print "testing unpack"
local unpack = table.unpack
local maxI = math.maxinteger
local minI = math.mininteger
local function checkerror (msg, f, ...)
local s, err = pcall(f, ...)
assert(not s and string.find(err, msg))
end
checkerror("wrong number of arguments", table.insert, {}, 2, 3, 4)
local x,y,z,a,n
a = {}; lim = _soft and 200 or 2000
for i=1, lim do a[i]=i end
assert(select(lim, unpack(a)) == lim and select('#', unpack(a)) == lim)
x = unpack(a)
assert(x == 1)
x = {unpack(a)}
assert(#x == lim and x[1] == 1 and x[lim] == lim)
x = {unpack(a, lim-2)}
assert(#x == 3 and x[1] == lim-2 and x[3] == lim)
x = {unpack(a, 10, 6)}
assert(next(x) == nil) -- no elements
x = {unpack(a, 11, 10)}
assert(next(x) == nil) -- no elements
x,y = unpack(a, 10, 10)
assert(x == 10 and y == nil)
x,y,z = unpack(a, 10, 11)
assert(x == 10 and y == 11 and z == nil)
a,x = unpack{1}
assert(a==1 and x==nil)
a,x = unpack({1,2}, 1, 1)
assert(a==1 and x==nil)
do
local maxi = (1 << 31) - 1 -- maximum value for an int (usually)
local mini = -(1 << 31) -- minimum value for an int (usually)
checkerror("too many results", unpack, {}, 0, maxi)
checkerror("too many results", unpack, {}, 1, maxi)
checkerror("too many results", unpack, {}, 0, maxI)
checkerror("too many results", unpack, {}, 1, maxI)
checkerror("too many results", unpack, {}, mini, maxi)
checkerror("too many results", unpack, {}, -maxi, maxi)
checkerror("too many results", unpack, {}, minI, maxI)
unpack({}, maxi, 0)
unpack({}, maxi, 1)
unpack({}, maxI, minI)
pcall(unpack, {}, 1, maxi + 1)
local a, b = unpack({[maxi] = 20}, maxi, maxi)
assert(a == 20 and b == nil)
a, b = unpack({[maxi] = 20}, maxi - 1, maxi)
assert(a == nil and b == 20)
local t = {[maxI - 1] = 12, [maxI] = 23}
a, b = unpack(t, maxI - 1, maxI); assert(a == 12 and b == 23)
a, b = unpack(t, maxI, maxI); assert(a == 23 and b == nil)
a, b = unpack(t, maxI, maxI - 1); assert(a == nil and b == nil)
t = {[minI] = 12.3, [minI + 1] = 23.5}
a, b = unpack(t, minI, minI + 1); assert(a == 12.3 and b == 23.5)
a, b = unpack(t, minI, minI); assert(a == 12.3 and b == nil)
a, b = unpack(t, minI + 1, minI); assert(a == nil and b == nil)
end
do -- length is not an integer
local t = setmetatable({}, {__len = function () return 'abc' end})
assert(#t == 'abc')
checkerror("object length is not an integer", table.insert, t, 1)
end
print "testing pack"
a = table.pack()
assert(a[1] == nil and a.n == 0)
a = table.pack(table)
assert(a[1] == table and a.n == 1)
a = table.pack(nil, nil, nil, nil)
assert(a[1] == nil and a.n == 4)
-- testing move
do
checkerror("table expected", table.move, 1, 2, 3, 4)
local function eqT (a, b)
for k, v in pairs(a) do assert(b[k] == v) end
for k, v in pairs(b) do assert(a[k] == v) end
end
local a = table.move({10,20,30}, 1, 3, 2) -- move forward
eqT(a, {10,10,20,30})
-- move forward with overlap of 1
a = table.move({10, 20, 30}, 1, 3, 3)
eqT(a, {10, 20, 10, 20, 30})
-- moving to the same table (not being explicit about it)
a = {10, 20, 30, 40}
table.move(a, 1, 4, 2, a)
eqT(a, {10, 10, 20, 30, 40})
a = table.move({10,20,30}, 2, 3, 1) -- move backward
eqT(a, {20,30,30})
a = {} -- move to new table
assert(table.move({10,20,30}, 1, 3, 1, a) == a)
eqT(a, {10,20,30})
a = {}
assert(table.move({10,20,30}, 1, 0, 3, a) == a) -- empty move (no move)
eqT(a, {})
a = table.move({10,20,30}, 1, 10, 1) -- move to the same place
eqT(a, {10,20,30})
-- moving on the fringes
a = table.move({[maxI - 2] = 1, [maxI - 1] = 2, [maxI] = 3},
maxI - 2, maxI, -10, {})
eqT(a, {[-10] = 1, [-9] = 2, [-8] = 3})
a = table.move({[minI] = 1, [minI + 1] = 2, [minI + 2] = 3},
minI, minI + 2, -10, {})
eqT(a, {[-10] = 1, [-9] = 2, [-8] = 3})
a = table.move({45}, 1, 1, maxI)
eqT(a, {45, [maxI] = 45})
a = table.move({[maxI] = 100}, maxI, maxI, minI)
eqT(a, {[minI] = 100, [maxI] = 100})
a = table.move({[minI] = 100}, minI, minI, maxI)
eqT(a, {[minI] = 100, [maxI] = 100})
a = setmetatable({}, {
__index = function (_,k) return k * 10 end,
__newindex = error})
local b = table.move(a, 1, 10, 3, {})
eqT(a, {})
eqT(b, {nil,nil,10,20,30,40,50,60,70,80,90,100})
b = setmetatable({""}, {
__index = error,
__newindex = function (t,k,v)
t[1] = string.format("%s(%d,%d)", t[1], k, v)
end})
table.move(a, 10, 13, 3, b)
assert(b[1] == "(3,100)(4,110)(5,120)(6,130)")
local stat, msg = pcall(table.move, b, 10, 13, 3, b)
assert(not stat and msg == b)
end
do
-- for very long moves, just check initial accesses and interrupt
-- move with an error
local function checkmove (f, e, t, x, y)
local pos1, pos2
local a = setmetatable({}, {
__index = function (_,k) pos1 = k end,
__newindex = function (_,k) pos2 = k; error() end, })
local st, msg = pcall(table.move, a, f, e, t)
assert(not st and not msg and pos1 == x and pos2 == y)
end
checkmove(1, maxI, 0, 1, 0)
checkmove(0, maxI - 1, 1, maxI - 1, maxI)
checkmove(minI, -2, -5, -2, maxI - 6)
checkmove(minI + 1, -1, -2, -1, maxI - 3)
checkmove(minI, -2, 0, minI, 0) -- non overlapping
checkmove(minI + 1, -1, 1, minI + 1, 1) -- non overlapping
end
checkerror("too many", table.move, {}, 0, maxI, 1)
checkerror("too many", table.move, {}, -1, maxI - 1, 1)
checkerror("too many", table.move, {}, minI, -1, 1)
checkerror("too many", table.move, {}, minI, maxI, 1)
checkerror("wrap around", table.move, {}, 1, maxI, 2)
checkerror("wrap around", table.move, {}, 1, 2, maxI)
checkerror("wrap around", table.move, {}, minI, -2, 2)
print"testing sort"
-- strange lengths
local a = setmetatable({}, {__len = function () return -1 end})
assert(#a == -1)
table.sort(a, error) -- should not compare anything
a = setmetatable({}, {__len = function () return maxI end})
checkerror("too big", table.sort, a)
-- test checks for invalid order functions
local function check (t)
local function f(a, b) assert(a and b); return true end
checkerror("invalid order function", table.sort, t, f)
end
check{1,2,3,4}
check{1,2,3,4,5}
check{1,2,3,4,5,6}
function check (a, f)
f = f or function (x,y) return x<y end;
for n = #a, 2, -1 do
assert(not f(a[n], a[n-1]))
end
end
a = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
"Oct", "Nov", "Dec"}
table.sort(a)
check(a)
function perm (s, n)
n = n or #s
if n == 1 then
local t = {unpack(s)}
table.sort(t)
check(t)
else
for i = 1, n do
s[i], s[n] = s[n], s[i]
perm(s, n - 1)
s[i], s[n] = s[n], s[i]
end
end
end
perm{}
perm{1}
perm{1,2}
perm{1,2,3}
perm{1,2,3,4}
perm{2,2,3,4}
perm{1,2,3,4,5}
perm{1,2,3,3,5}
perm{1,2,3,4,5,6}
perm{2,2,3,3,5,6}
function timesort (a, n, func, msg, pre)
local x = os.clock()
table.sort(a, func)
x = (os.clock() - x) * 1000
pre = pre or ""
print(string.format("%ssorting %d %s elements in %.2f msec.", pre, n, msg, x))
check(a, func)
end
limit = 50000
if _soft then limit = 5000 end
a = {}
for i=1,limit do
a[i] = math.random()
end
timesort(a, limit, nil, "random")
timesort(a, limit, nil, "sorted", "re-")
a = {}
for i=1,limit do
a[i] = math.random()
end
x = os.clock(); i=0
table.sort(a, function(x,y) i=i+1; return y<x end)
x = (os.clock() - x) * 1000
print(string.format("Invert-sorting other %d elements in %.2f msec., with %i comparisons",
limit, x, i))
check(a, function(x,y) return y<x end)
table.sort{} -- empty array
for i=1,limit do a[i] = false end
timesort(a, limit, function(x,y) return nil end, "equal")
for i,v in pairs(a) do assert(v == false) end
A = {"álo", "\0first :-)", "alo", "then this one", "45", "and a new"}
table.sort(A)
check(A)
table.sort(A, function (x, y)
load(string.format("A[%q] = ''", x), "")()
collectgarbage()
return x<y
end)
tt = {__lt = function (a,b) return a.val < b.val end}
a = {}
for i=1,10 do a[i] = {val=math.random(100)}; setmetatable(a[i], tt); end
table.sort(a)
check(a, tt.__lt)
check(a)
print"OK"

@ -0,0 +1,379 @@
-- $Id: strings.lua,v 1.87 2016/12/21 19:23:02 roberto Exp $
-- See Copyright Notice in file all.lua
print('testing strings and string library')
local maxi, mini = math.maxinteger, math.mininteger
local function checkerror (msg, f, ...)
local s, err = pcall(f, ...)
assert(not s and string.find(err, msg))
end
-- testing string comparisons
assert('alo' < 'alo1')
assert('' < 'a')
assert('alo\0alo' < 'alo\0b')
assert('alo\0alo\0\0' > 'alo\0alo\0')
assert('alo' < 'alo\0')
assert('alo\0' > 'alo')
assert('\0' < '\1')
assert('\0\0' < '\0\1')
assert('\1\0a\0a' <= '\1\0a\0a')
assert(not ('\1\0a\0b' <= '\1\0a\0a'))
assert('\0\0\0' < '\0\0\0\0')
assert(not('\0\0\0\0' < '\0\0\0'))
assert('\0\0\0' <= '\0\0\0\0')
assert(not('\0\0\0\0' <= '\0\0\0'))
assert('\0\0\0' <= '\0\0\0')
assert('\0\0\0' >= '\0\0\0')
assert(not ('\0\0b' < '\0\0a\0'))
-- testing string.sub
assert(string.sub("123456789",2,4) == "234")
assert(string.sub("123456789",7) == "789")
assert(string.sub("123456789",7,6) == "")
assert(string.sub("123456789",7,7) == "7")
assert(string.sub("123456789",0,0) == "")
assert(string.sub("123456789",-10,10) == "123456789")
assert(string.sub("123456789",1,9) == "123456789")
assert(string.sub("123456789",-10,-20) == "")
assert(string.sub("123456789",-1) == "9")
assert(string.sub("123456789",-4) == "6789")
assert(string.sub("123456789",-6, -4) == "456")
assert(string.sub("123456789", mini, -4) == "123456")
assert(string.sub("123456789", mini, maxi) == "123456789")
assert(string.sub("123456789", mini, mini) == "")
assert(string.sub("\000123456789",3,5) == "234")
assert(("\000123456789"):sub(8) == "789")
-- testing string.find
assert(string.find("123456789", "345") == 3)
a,b = string.find("123456789", "345")
assert(string.sub("123456789", a, b) == "345")
assert(string.find("1234567890123456789", "345", 3) == 3)
assert(string.find("1234567890123456789", "345", 4) == 13)
assert(string.find("1234567890123456789", "346", 4) == nil)
assert(string.find("1234567890123456789", ".45", -9) == 13)
assert(string.find("abcdefg", "\0", 5, 1) == nil)
assert(string.find("", "") == 1)
assert(string.find("", "", 1) == 1)
assert(not string.find("", "", 2))
assert(string.find('', 'aaa', 1) == nil)
assert(('alo(.)alo'):find('(.)', 1, 1) == 4)
assert(string.len("") == 0)
assert(string.len("\0\0\0") == 3)
assert(string.len("1234567890") == 10)
assert(#"" == 0)
assert(#"\0\0\0" == 3)
assert(#"1234567890" == 10)
-- testing string.byte/string.char
assert(string.byte("a") == 97)
assert(string.byte("\xe4") > 127)
assert(string.byte(string.char(255)) == 255)
assert(string.byte(string.char(0)) == 0)
assert(string.byte("\0") == 0)
assert(string.byte("\0\0alo\0x", -1) == string.byte('x'))
assert(string.byte("ba", 2) == 97)
assert(string.byte("\n\n", 2, -1) == 10)
assert(string.byte("\n\n", 2, 2) == 10)
assert(string.byte("") == nil)
assert(string.byte("hi", -3) == nil)
assert(string.byte("hi", 3) == nil)
assert(string.byte("hi", 9, 10) == nil)
assert(string.byte("hi", 2, 1) == nil)
assert(string.char() == "")
assert(string.char(0, 255, 0) == "\0\255\0")
assert(string.char(0, string.byte("\xe4"), 0) == "\0\xe4\0")
assert(string.char(string.byte("\xe4l\0óu", 1, -1)) == "\xe4l\0óu")
assert(string.char(string.byte("\xe4l\0óu", 1, 0)) == "")
assert(string.char(string.byte("\xe4l\0óu", -10, 100)) == "\xe4l\0óu")
assert(string.upper("ab\0c") == "AB\0C")
assert(string.lower("\0ABCc%$") == "\0abcc%$")
assert(string.rep('teste', 0) == '')
assert(string.rep('tés\00', 2) == 'tés\0têtés\000')
assert(string.rep('', 10) == '')
if string.packsize("i") == 4 then
-- result length would be 2^31 (int overflow)
checkerror("too large", string.rep, 'aa', (1 << 30))
checkerror("too large", string.rep, 'a', (1 << 30), ',')
end
-- repetitions with separator
assert(string.rep('teste', 0, 'xuxu') == '')
assert(string.rep('teste', 1, 'xuxu') == 'teste')
assert(string.rep('\1\0\1', 2, '\0\0') == '\1\0\1\0\0\1\0\1')
assert(string.rep('', 10, '.') == string.rep('.', 9))
assert(not pcall(string.rep, "aa", maxi // 2 + 10))
assert(not pcall(string.rep, "", maxi // 2 + 10, "aa"))
assert(string.reverse"" == "")
assert(string.reverse"\0\1\2\3" == "\3\2\1\0")
assert(string.reverse"\0001234" == "4321\0")
for i=0,30 do assert(string.len(string.rep('a', i)) == i) end
assert(type(tostring(nil)) == 'string')
assert(type(tostring(12)) == 'string')
assert(string.find(tostring{}, 'table:'))
assert(string.find(tostring(print), 'function:'))
assert(#tostring('\0') == 1)
assert(tostring(true) == "true")
assert(tostring(false) == "false")
assert(tostring(-1203) == "-1203")
assert(tostring(1203.125) == "1203.125")
assert(tostring(-0.5) == "-0.5")
assert(tostring(-32767) == "-32767")
if math.tointeger(2147483647) then -- no overflow? (32 bits)
assert(tostring(-2147483647) == "-2147483647")
end
if math.tointeger(4611686018427387904) then -- no overflow? (64 bits)
assert(tostring(4611686018427387904) == "4611686018427387904")
assert(tostring(-4611686018427387904) == "-4611686018427387904")
end
if tostring(0.0) == "0.0" then -- "standard" coercion float->string
assert('' .. 12 == '12' and 12.0 .. '' == '12.0')
assert(tostring(-1203 + 0.0) == "-1203.0")
else -- compatible coercion
assert(tostring(0.0) == "0")
assert('' .. 12 == '12' and 12.0 .. '' == '12')
assert(tostring(-1203 + 0.0) == "-1203")
end
x = '"ílo"\n\\'
assert(string.format('%q%s', x, x) == '"\\"ílo\\"\\\n\\\\""ílo"\n\\')
assert(string.format('%q', "\0") == [["\0"]])
assert(load(string.format('return %q', x))() == x)
x = "\0\1\0023\5\0009"
assert(load(string.format('return %q', x))() == x)
assert(string.format("\0%c\0%c%x\0", string.byte("\xe4"), string.byte("b"), 140) ==
"\0\xe4\0b8c\0")
assert(string.format('') == "")
assert(string.format("%c",34)..string.format("%c",48)..string.format("%c",90)..string.format("%c",100) ==
string.format("%c%c%c%c", 34, 48, 90, 100))
assert(string.format("%s\0 is not \0%s", 'not be', 'be') == 'not be\0 is not \0be')
assert(string.format("%%%d %010d", 10, 23) == "%10 0000000023")
assert(tonumber(string.format("%f", 10.3)) == 10.3)
x = string.format('"%-50s"', 'a')
assert(#x == 52)
assert(string.sub(x, 1, 4) == '"a ')
assert(string.format("-%.20s.20s", string.rep("%", 2000)) ==
"-"..string.rep("%", 20)..".20s")
assert(string.format('"-%20s.20s"', string.rep("%", 2000)) ==
string.format("%q", "-"..string.rep("%", 2000)..".20s"))
do
local function checkQ (v)
local s = string.format("%q", v)
local nv = load("return " .. s)()
assert(v == nv and math.type(v) == math.type(nv))
end
checkQ("\0\0\1\255\u{234}")
checkQ(math.maxinteger)
checkQ(math.mininteger)
checkQ(math.pi)
checkQ(0.1)
checkQ(true)
checkQ(nil)
checkQ(false)
checkerror("no literal", string.format, "%q", {})
end
assert(string.format("\0%s\0", "\0\0\1") == "\0\0\0\1\0")
checkerror("contains zeros", string.format, "%10s", "\0")
-- format x tostring
assert(string.format("%s %s", nil, true) == "nil true")
assert(string.format("%s %.4s", false, true) == "false true")
assert(string.format("%.3s %.3s", false, true) == "fal tru")
local m = setmetatable({}, {__tostring = function () return "hello" end,
__name = "hi"})
assert(string.format("%s %.10s", m, m) == "hello hello")
getmetatable(m).__tostring = nil -- will use '__name' from now on
assert(string.format("%.4s", m) == "hi: ")
getmetatable(m).__tostring = function () return {} end
checkerror("'__tostring' must return a string", tostring, m)
assert(string.format("%x", 0.0) == "0")
assert(string.format("%02x", 0.0) == "00")
assert(string.format("%08X", 0xFFFFFFFF) == "FFFFFFFF")
assert(string.format("%+08d", 31501) == "+0031501")
assert(string.format("%+08d", -30927) == "-0030927")
do -- longest number that can be formatted
local i = 1
local j = 10000
while i + 1 < j do -- binary search for maximum finite float
local m = (i + j) // 2
if 10^m < math.huge then i = m else j = m end
end
assert(10^i < math.huge and 10^j == math.huge)
local s = string.format('%.99f', -(10^i))
assert(string.len(s) >= i + 101)
assert(tonumber(s) == -(10^i))
end
-- testing large numbers for format
do -- assume at least 32 bits
local max, min = 0x7fffffff, -0x80000000 -- "large" for 32 bits
assert(string.sub(string.format("%8x", -1), -8) == "ffffffff")
assert(string.format("%x", max) == "7fffffff")
assert(string.sub(string.format("%x", min), -8) == "80000000")
assert(string.format("%d", max) == "2147483647")
assert(string.format("%d", min) == "-2147483648")
assert(string.format("%u", 0xffffffff) == "4294967295")
assert(string.format("%o", 0xABCD) == "125715")
max, min = 0x7fffffffffffffff, -0x8000000000000000
if max > 2.0^53 then -- only for 64 bits
assert(string.format("%x", (2^52 | 0) - 1) == "fffffffffffff")
assert(string.format("0x%8X", 0x8f000003) == "0x8F000003")
assert(string.format("%d", 2^53) == "9007199254740992")
assert(string.format("%i", -2^53) == "-9007199254740992")
assert(string.format("%x", max) == "7fffffffffffffff")
assert(string.format("%x", min) == "8000000000000000")
assert(string.format("%d", max) == "9223372036854775807")
assert(string.format("%d", min) == "-9223372036854775808")
assert(string.format("%u", ~(-1 << 64)) == "18446744073709551615")
assert(tostring(1234567890123) == '1234567890123')
end
end
do print("testing 'format %a %A'")
local function matchhexa (n)
local s = string.format("%a", n)
-- result matches ISO C requirements
assert(string.find(s, "^%-?0x[1-9a-f]%.?[0-9a-f]*p[-+]?%d+$"))
assert(tonumber(s) == n) -- and has full precision
s = string.format("%A", n)
assert(string.find(s, "^%-?0X[1-9A-F]%.?[0-9A-F]*P[-+]?%d+$"))
assert(tonumber(s) == n)
end
for _, n in ipairs{0.1, -0.1, 1/3, -1/3, 1e30, -1e30,
-45/247, 1, -1, 2, -2, 3e-20, -3e-20} do
matchhexa(n)
end
assert(string.find(string.format("%A", 0.0), "^0X0%.?0?P%+?0$"))
assert(string.find(string.format("%a", -0.0), "^%-0x0%.?0?p%+?0$"))
if not _port then -- test inf, -inf, NaN, and -0.0
assert(string.find(string.format("%a", 1/0), "^inf"))
assert(string.find(string.format("%A", -1/0), "^%-INF"))
assert(string.find(string.format("%a", 0/0), "^%-?nan"))
assert(string.find(string.format("%a", -0.0), "^%-0x0"))
end
if not pcall(string.format, "%.3a", 0) then
(Message or print)("\n >>> modifiers for format '%a' not available <<<\n")
else
assert(string.find(string.format("%+.2A", 12), "^%+0X%x%.%x0P%+?%d$"))
assert(string.find(string.format("%.4A", -12), "^%-0X%x%.%x000P%+?%d$"))
end
end
-- errors in format
local function check (fmt, msg)
checkerror(msg, string.format, fmt, 10)
end
local aux = string.rep('0', 600)
check("%100.3d", "too long")
check("%1"..aux..".3d", "too long")
check("%1.100d", "too long")
check("%10.1"..aux.."004d", "too long")
check("%t", "invalid option")
check("%"..aux.."d", "repeated flags")
check("%d %d", "no value")
assert(load("return 1\n--comment without ending EOL")() == 1)
checkerror("table expected", table.concat, 3)
assert(table.concat{} == "")
assert(table.concat({}, 'x') == "")
assert(table.concat({'\0', '\0\1', '\0\1\2'}, '.\0.') == "\0.\0.\0\1.\0.\0\1\2")
local a = {}; for i=1,300 do a[i] = "xuxu" end
assert(table.concat(a, "123").."123" == string.rep("xuxu123", 300))
assert(table.concat(a, "b", 20, 20) == "xuxu")
assert(table.concat(a, "", 20, 21) == "xuxuxuxu")
assert(table.concat(a, "x", 22, 21) == "")
assert(table.concat(a, "3", 299) == "xuxu3xuxu")
assert(table.concat({}, "x", maxi, maxi - 1) == "")
assert(table.concat({}, "x", mini + 1, mini) == "")
assert(table.concat({}, "x", maxi, mini) == "")
assert(table.concat({[maxi] = "alo"}, "x", maxi, maxi) == "alo")
assert(table.concat({[maxi] = "alo", [maxi - 1] = "y"}, "-", maxi - 1, maxi)
== "y-alo")
assert(not pcall(table.concat, {"a", "b", {}}))
a = {"a","b","c"}
assert(table.concat(a, ",", 1, 0) == "")
assert(table.concat(a, ",", 1, 1) == "a")
assert(table.concat(a, ",", 1, 2) == "a,b")
assert(table.concat(a, ",", 2) == "b,c")
assert(table.concat(a, ",", 3) == "c")
assert(table.concat(a, ",", 4) == "")
if not _port then
local locales = { "ptb", "pt_BR.iso88591", "ISO-8859-1" }
local function trylocale (w)
for i = 1, #locales do
if os.setlocale(locales[i], w) then
print(string.format("'%s' locale set to '%s'", w, locales[i]))
return locales[i]
end
end
print(string.format("'%s' locale not found", w))
return false
end
if trylocale("collate") then
assert("alo" < "álo" and "álo" < "amo")
end
if trylocale("ctype") then
assert(string.gsub("áéíóú", "%a", "x") == "xxxxx")
assert(string.gsub("áÁéÉ", "%l", "x") == "xÁxÉ")
assert(string.gsub("áÁéÉ", "%u", "x") == "áxéx")
assert(string.upper"áÁé{xuxu}ção" == "ÁÁÉ{XUXU}ÇÃO")
end
os.setlocale("C")
assert(os.setlocale() == 'C')
assert(os.setlocale(nil, "numeric") == 'C')
end
-- bug in Lua 5.3.2
-- 'gmatch' iterator does not work across coroutines
do
local f = string.gmatch("1 2 3 4 5", "%d+")
assert(f() == "1")
co = coroutine.wrap(f)
assert(co() == "2")
end
print('OK')

@ -0,0 +1,322 @@
-- $Id: tpack.lua,v 1.13 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
local pack = string.pack
local packsize = string.packsize
local unpack = string.unpack
print "testing pack/unpack"
-- maximum size for integers
local NB = 16
local sizeshort = packsize("h")
local sizeint = packsize("i")
local sizelong = packsize("l")
local sizesize_t = packsize("T")
local sizeLI = packsize("j")
local sizefloat = packsize("f")
local sizedouble = packsize("d")
local sizenumber = packsize("n")
local little = (pack("i2", 1) == "\1\0")
local align = packsize("!xXi16")
assert(1 <= sizeshort and sizeshort <= sizeint and sizeint <= sizelong and
sizefloat <= sizedouble)
print("platform:")
print(string.format(
"\tshort %d, int %d, long %d, size_t %d, float %d, double %d,\n\z
\tlua Integer %d, lua Number %d",
sizeshort, sizeint, sizelong, sizesize_t, sizefloat, sizedouble,
sizeLI, sizenumber))
print("\t" .. (little and "little" or "big") .. " endian")
print("\talignment: " .. align)
-- check errors in arguments
function checkerror (msg, f, ...)
local status, err = pcall(f, ...)
-- print(status, err, msg)
assert(not status and string.find(err, msg))
end
-- minimum behavior for integer formats
assert(unpack("B", pack("B", 0xff)) == 0xff)
assert(unpack("b", pack("b", 0x7f)) == 0x7f)
assert(unpack("b", pack("b", -0x80)) == -0x80)
assert(unpack("H", pack("H", 0xffff)) == 0xffff)
assert(unpack("h", pack("h", 0x7fff)) == 0x7fff)
assert(unpack("h", pack("h", -0x8000)) == -0x8000)
assert(unpack("L", pack("L", 0xffffffff)) == 0xffffffff)
assert(unpack("l", pack("l", 0x7fffffff)) == 0x7fffffff)
assert(unpack("l", pack("l", -0x80000000)) == -0x80000000)
for i = 1, NB do
-- small numbers with signal extension ("\xFF...")
local s = string.rep("\xff", i)
assert(pack("i" .. i, -1) == s)
assert(packsize("i" .. i) == #s)
assert(unpack("i" .. i, s) == -1)
-- small unsigned number ("\0...\xAA")
s = "\xAA" .. string.rep("\0", i - 1)
assert(pack("<I" .. i, 0xAA) == s)
assert(unpack("<I" .. i, s) == 0xAA)
assert(pack(">I" .. i, 0xAA) == s:reverse())
assert(unpack(">I" .. i, s:reverse()) == 0xAA)
end
do
local lnum = 0x13121110090807060504030201
local s = pack("<j", lnum)
assert(unpack("<j", s) == lnum)
assert(unpack("<i" .. sizeLI + 1, s .. "\0") == lnum)
assert(unpack("<i" .. sizeLI + 1, s .. "\0") == lnum)
for i = sizeLI + 1, NB do
local s = pack("<j", -lnum)
assert(unpack("<j", s) == -lnum)
-- strings with (correct) extra bytes
assert(unpack("<i" .. i, s .. ("\xFF"):rep(i - sizeLI)) == -lnum)
assert(unpack(">i" .. i, ("\xFF"):rep(i - sizeLI) .. s:reverse()) == -lnum)
assert(unpack("<I" .. i, s .. ("\0"):rep(i - sizeLI)) == -lnum)
-- overflows
checkerror("does not fit", unpack, "<I" .. i, ("\x00"):rep(i - 1) .. "\1")
checkerror("does not fit", unpack, ">i" .. i, "\1" .. ("\x00"):rep(i - 1))
end
end
for i = 1, sizeLI do
local lstr = "\1\2\3\4\5\6\7\8\9\10\11\12\13"
local lnum = 0x13121110090807060504030201
local n = lnum & (~(-1 << (i * 8)))
local s = string.sub(lstr, 1, i)
assert(pack("<i" .. i, n) == s)
assert(pack(">i" .. i, n) == s:reverse())
assert(unpack(">i" .. i, s:reverse()) == n)
end
-- sign extension
do
local u = 0xf0
for i = 1, sizeLI - 1 do
assert(unpack("<i"..i, "\xf0"..("\xff"):rep(i - 1)) == -16)
assert(unpack(">I"..i, "\xf0"..("\xff"):rep(i - 1)) == u)
u = u * 256 + 0xff
end
end
-- mixed endianness
do
assert(pack(">i2 <i2", 10, 20) == "\0\10\20\0")
local a, b = unpack("<i2 >i2", "\10\0\0\20")
assert(a == 10 and b == 20)
assert(pack("=i4", 2001) == pack("i4", 2001))
end
print("testing invalid formats")
checkerror("out of limits", pack, "i0", 0)
checkerror("out of limits", pack, "i" .. NB + 1, 0)
checkerror("out of limits", pack, "!" .. NB + 1, 0)
checkerror("%(17%) out of limits %[1,16%]", pack, "Xi" .. NB + 1)
checkerror("invalid format option 'r'", pack, "i3r", 0)
checkerror("16%-byte integer", unpack, "i16", string.rep('\3', 16))
checkerror("not power of 2", pack, "!4i3", 0);
checkerror("missing size", pack, "c", "")
checkerror("variable%-length format", packsize, "s")
checkerror("variable%-length format", packsize, "z")
-- overflow in option size (error will be in digit after limit)
checkerror("invalid format", packsize, "c1" .. string.rep("0", 40))
if packsize("i") == 4 then
-- result would be 2^31 (2^3 repetitions of 2^28 strings)
local s = string.rep("c268435456", 2^3)
checkerror("too large", packsize, s)
-- one less is OK
s = string.rep("c268435456", 2^3 - 1) .. "c268435455"
assert(packsize(s) == 0x7fffffff)
end
-- overflow in packing
for i = 1, sizeLI - 1 do
local umax = (1 << (i * 8)) - 1
local max = umax >> 1
local min = ~max
checkerror("overflow", pack, "<I" .. i, -1)
checkerror("overflow", pack, "<I" .. i, min)
checkerror("overflow", pack, ">I" .. i, umax + 1)
checkerror("overflow", pack, ">i" .. i, umax)
checkerror("overflow", pack, ">i" .. i, max + 1)
checkerror("overflow", pack, "<i" .. i, min - 1)
assert(unpack(">i" .. i, pack(">i" .. i, max)) == max)
assert(unpack("<i" .. i, pack("<i" .. i, min)) == min)
assert(unpack(">I" .. i, pack(">I" .. i, umax)) == umax)
end
-- Lua integer size
assert(unpack(">j", pack(">j", math.maxinteger)) == math.maxinteger)
assert(unpack("<j", pack("<j", math.mininteger)) == math.mininteger)
assert(unpack("<J", pack("<j", -1)) == -1) -- maximum unsigned integer
if little then
assert(pack("f", 24) == pack("<f", 24))
else
assert(pack("f", 24) == pack(">f", 24))
end
print "testing pack/unpack of floating-point numbers"
for _, n in ipairs{0, -1.1, 1.9, 1/0, -1/0, 1e20, -1e20, 0.1, 2000.7} do
assert(unpack("n", pack("n", n)) == n)
assert(unpack("<n", pack("<n", n)) == n)
assert(unpack(">n", pack(">n", n)) == n)
assert(pack("<f", n) == pack(">f", n):reverse())
assert(pack(">d", n) == pack("<d", n):reverse())
end
-- for non-native precisions, test only with "round" numbers
for _, n in ipairs{0, -1.5, 1/0, -1/0, 1e10, -1e9, 0.5, 2000.25} do
assert(unpack("<f", pack("<f", n)) == n)
assert(unpack(">f", pack(">f", n)) == n)
assert(unpack("<d", pack("<d", n)) == n)
assert(unpack(">d", pack(">d", n)) == n)
end
print "testing pack/unpack of strings"
do
local s = string.rep("abc", 1000)
assert(pack("zB", s, 247) == s .. "\0\xF7")
local s1, b = unpack("zB", s .. "\0\xF9")
assert(b == 249 and s1 == s)
s1 = pack("s", s)
assert(unpack("s", s1) == s)
checkerror("does not fit", pack, "s1", s)
checkerror("contains zeros", pack, "z", "alo\0");
for i = 2, NB do
local s1 = pack("s" .. i, s)
assert(unpack("s" .. i, s1) == s and #s1 == #s + i)
end
end
do
local x = pack("s", "alo")
checkerror("too short", unpack, "s", x:sub(1, -2))
checkerror("too short", unpack, "c5", "abcd")
checkerror("out of limits", pack, "s100", "alo")
end
do
assert(pack("c0", "") == "")
assert(packsize("c0") == 0)
assert(unpack("c0", "") == "")
assert(pack("<! c3", "abc") == "abc")
assert(packsize("<! c3") == 3)
assert(pack(">!4 c6", "abcdef") == "abcdef")
assert(pack("c3", "123") == "123")
assert(pack("c0", "") == "")
assert(pack("c8", "123456") == "123456\0\0")
assert(pack("c88", "") == string.rep("\0", 88))
assert(pack("c188", "ab") == "ab" .. string.rep("\0", 188 - 2))
local a, b, c = unpack("!4 z c3", "abcdefghi\0xyz")
assert(a == "abcdefghi" and b == "xyz" and c == 14)
checkerror("longer than", pack, "c3", "1234")
end
-- testing multiple types and sequence
do
local x = pack("<b h b f d f n i", 1, 2, 3, 4, 5, 6, 7, 8)
assert(#x == packsize("<b h b f d f n i"))
local a, b, c, d, e, f, g, h = unpack("<b h b f d f n i", x)
assert(a == 1 and b == 2 and c == 3 and d == 4 and e == 5 and f == 6 and
g == 7 and h == 8)
end
print "testing alignment"
do
assert(pack(" < i1 i2 ", 2, 3) == "\2\3\0") -- no alignment by default
local x = pack(">!8 b Xh i4 i8 c1 Xi8", -12, 100, 200, "\xEC")
assert(#x == packsize(">!8 b Xh i4 i8 c1 Xi8"))
assert(x == "\xf4" .. "\0\0\0" ..
"\0\0\0\100" ..
"\0\0\0\0\0\0\0\xC8" ..
"\xEC" .. "\0\0\0\0\0\0\0")
local a, b, c, d, pos = unpack(">!8 c1 Xh i4 i8 b Xi8 XI XH", x)
assert(a == "\xF4" and b == 100 and c == 200 and d == -20 and (pos - 1) == #x)
x = pack(">!4 c3 c4 c2 z i4 c5 c2 Xi4",
"abc", "abcd", "xz", "hello", 5, "world", "xy")
assert(x == "abcabcdxzhello\0\0\0\0\0\5worldxy\0")
local a, b, c, d, e, f, g, pos = unpack(">!4 c3 c4 c2 z i4 c5 c2 Xh Xi4", x)
assert(a == "abc" and b == "abcd" and c == "xz" and d == "hello" and
e == 5 and f == "world" and g == "xy" and (pos - 1) % 4 == 0)
x = pack(" b b Xd b Xb x", 1, 2, 3)
assert(packsize(" b b Xd b Xb x") == 4)
assert(x == "\1\2\3\0")
a, b, c, pos = unpack("bbXdb", x)
assert(a == 1 and b == 2 and c == 3 and pos == #x)
-- only alignment
assert(packsize("!8 xXi8") == 8)
local pos = unpack("!8 xXi8", "0123456701234567"); assert(pos == 9)
assert(packsize("!8 xXi2") == 2)
local pos = unpack("!8 xXi2", "0123456701234567"); assert(pos == 3)
assert(packsize("!2 xXi2") == 2)
local pos = unpack("!2 xXi2", "0123456701234567"); assert(pos == 3)
assert(packsize("!2 xXi8") == 2)
local pos = unpack("!2 xXi8", "0123456701234567"); assert(pos == 3)
assert(packsize("!16 xXi16") == 16)
local pos = unpack("!16 xXi16", "0123456701234567"); assert(pos == 17)
checkerror("invalid next option", pack, "X")
checkerror("invalid next option", unpack, "XXi", "")
checkerror("invalid next option", unpack, "X i", "")
checkerror("invalid next option", pack, "Xc1")
end
do -- testing initial position
local x = pack("i4i4i4i4", 1, 2, 3, 4)
for pos = 1, 16, 4 do
local i, p = unpack("i4", x, pos)
assert(i == pos//4 + 1 and p == pos + 4)
end
-- with alignment
for pos = 0, 12 do -- will always round position to power of 2
local i, p = unpack("!4 i4", x, pos + 1)
assert(i == (pos + 3)//4 + 1 and p == i*4 + 1)
end
-- negative indices
local i, p = unpack("!4 i4", x, -4)
assert(i == 4 and p == 17)
local i, p = unpack("!4 i4", x, -7)
assert(i == 4 and p == 17)
local i, p = unpack("!4 i4", x, -#x)
assert(i == 1 and p == 5)
-- limits
for i = 1, #x + 1 do
assert(unpack("c0", x, i) == "")
end
checkerror("out of string", unpack, "c0", x, 0)
checkerror("out of string", unpack, "c0", x, #x + 2)
checkerror("out of string", unpack, "c0", x, -(#x + 1))
end
print "OK"

@ -0,0 +1,210 @@
-- $Id: utf8.lua,v 1.12 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print "testing UTF-8 library"
local utf8 = require'utf8'
local function checkerror (msg, f, ...)
local s, err = pcall(f, ...)
assert(not s and string.find(err, msg))
end
local function len (s)
return #string.gsub(s, "[\x80-\xBF]", "")
end
local justone = "^" .. utf8.charpattern .. "$"
-- 't' is the list of codepoints of 's'
local function checksyntax (s, t)
local ts = {"return '"}
for i = 1, #t do ts[i + 1] = string.format("\\u{%x}", t[i]) end
ts[#t + 2] = "'"
ts = table.concat(ts)
assert(assert(load(ts))() == s)
end
assert(utf8.offset("alo", 5) == nil)
assert(utf8.offset("alo", -4) == nil)
-- 't' is the list of codepoints of 's'
local function check (s, t)
local l = utf8.len(s)
assert(#t == l and len(s) == l)
assert(utf8.char(table.unpack(t)) == s)
assert(utf8.offset(s, 0) == 1)
checksyntax(s, t)
local t1 = {utf8.codepoint(s, 1, -1)}
assert(#t == #t1)
for i = 1, #t do assert(t[i] == t1[i]) end
for i = 1, l do
local pi = utf8.offset(s, i) -- position of i-th char
local pi1 = utf8.offset(s, 2, pi) -- position of next char
assert(string.find(string.sub(s, pi, pi1 - 1), justone))
assert(utf8.offset(s, -1, pi1) == pi)
assert(utf8.offset(s, i - l - 1) == pi)
assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi)))
for j = pi, pi1 - 1 do
assert(utf8.offset(s, 0, j) == pi)
end
for j = pi + 1, pi1 - 1 do
assert(not utf8.len(s, j))
end
assert(utf8.len(s, pi, pi) == 1)
assert(utf8.len(s, pi, pi1 - 1) == 1)
assert(utf8.len(s, pi) == l - i + 1)
assert(utf8.len(s, pi1) == l - i)
assert(utf8.len(s, 1, pi) == i)
end
local i = 0
for p, c in utf8.codes(s) do
i = i + 1
assert(c == t[i] and p == utf8.offset(s, i))
assert(utf8.codepoint(s, p) == c)
end
assert(i == #t)
i = 0
for p, c in utf8.codes(s) do
i = i + 1
assert(c == t[i] and p == utf8.offset(s, i))
end
assert(i == #t)
i = 0
for c in string.gmatch(s, utf8.charpattern) do
i = i + 1
assert(c == utf8.char(t[i]))
end
assert(i == #t)
for i = 1, l do
assert(utf8.offset(s, i) == utf8.offset(s, i - l - 1, #s + 1))
end
end
do -- error indication in utf8.len
local function check (s, p)
local a, b = utf8.len(s)
assert(not a and b == p)
end
check("abc\xE3def", 4)
check("汉字\x80", #("汉字") + 1)
check("\xF4\x9F\xBF", 1)
check("\xF4\x9F\xBF\xBF", 1)
end
-- error in utf8.codes
checkerror("invalid UTF%-8 code",
function ()
local s = "ab\xff"
for c in utf8.codes(s) do assert(c) end
end)
-- error in initial position for offset
checkerror("position out of range", utf8.offset, "abc", 1, 5)
checkerror("position out of range", utf8.offset, "abc", 1, -4)
checkerror("position out of range", utf8.offset, "", 1, 2)
checkerror("position out of range", utf8.offset, "", 1, -1)
checkerror("continuation byte", utf8.offset, "𦧺", 1, 2)
checkerror("continuation byte", utf8.offset, "𦧺", 1, 2)
checkerror("continuation byte", utf8.offset, "\x80", 1)
local s = "hello World"
local t = {string.byte(s, 1, -1)}
for i = 1, utf8.len(s) do assert(t[i] == string.byte(s, i)) end
check(s, t)
check("汉字/漢字", {27721, 23383, 47, 28450, 23383,})
do
local s = "áéí\128"
local t = {utf8.codepoint(s,1,#s - 1)}
assert(#t == 3 and t[1] == 225 and t[2] == 233 and t[3] == 237)
checkerror("invalid UTF%-8 code", utf8.codepoint, s, 1, #s)
checkerror("out of range", utf8.codepoint, s, #s + 1)
t = {utf8.codepoint(s, 4, 3)}
assert(#t == 0)
checkerror("out of range", utf8.codepoint, s, -(#s + 1), 1)
checkerror("out of range", utf8.codepoint, s, 1, #s + 1)
end
assert(utf8.char() == "")
assert(utf8.char(97, 98, 99) == "abc")
assert(utf8.codepoint(utf8.char(0x10FFFF)) == 0x10FFFF)
checkerror("value out of range", utf8.char, 0x10FFFF + 1)
local function invalid (s)
checkerror("invalid UTF%-8 code", utf8.codepoint, s)
assert(not utf8.len(s))
end
-- UTF-8 representation for 0x11ffff (value out of valid range)
invalid("\xF4\x9F\xBF\xBF")
-- overlong sequences
invalid("\xC0\x80") -- zero
invalid("\xC1\xBF") -- 0x7F (should be coded in 1 byte)
invalid("\xE0\x9F\xBF") -- 0x7FF (should be coded in 2 bytes)
invalid("\xF0\x8F\xBF\xBF") -- 0xFFFF (should be coded in 3 bytes)
-- invalid bytes
invalid("\x80") -- continuation byte
invalid("\xBF") -- continuation byte
invalid("\xFE") -- invalid byte
invalid("\xFF") -- invalid byte
-- empty string
check("", {})
-- minimum and maximum values for each sequence size
s = "\0 \x7F\z
\xC2\x80 \xDF\xBF\z
\xE0\xA0\x80 \xEF\xBF\xBF\z
\xF0\x90\x80\x80 \xF4\x8F\xBF\xBF"
s = string.gsub(s, " ", "")
check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF})
x = "日本語a-4\0éó"
check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243})
-- Supplementary Characters
check("𣲷𠜎𠱓𡁻𠵼ab𠺢",
{0x23CB7, 0x2070E, 0x20C53, 0x2107B, 0x20D7C, 0x61, 0x62, 0x20EA2,})
check("𨳊𩶘𦧺𨳒𥄫𤓓\xF4\x8F\xBF\xBF",
{0x28CCA, 0x29D98, 0x269FA, 0x28CD2, 0x2512B, 0x244D3, 0x10ffff})
local i = 0
for p, c in string.gmatch(x, "()(" .. utf8.charpattern .. ")") do
i = i + 1
assert(utf8.offset(x, i) == p)
assert(utf8.len(x, p) == utf8.len(x) - i + 1)
assert(utf8.len(c) == 1)
for j = 1, #c - 1 do
assert(utf8.offset(x, 0, p + j - 1) == p)
end
end
print'ok'

@ -0,0 +1,142 @@
-- $Id: vararg.lua,v 1.25 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print('testing vararg')
function f(a, ...)
local arg = {n = select('#', ...), ...}
for i=1,arg.n do assert(a[i]==arg[i]) end
return arg.n
end
function c12 (...)
assert(arg == _G.arg) -- no local 'arg'
local x = {...}; x.n = #x
local res = (x.n==2 and x[1] == 1 and x[2] == 2)
if res then res = 55 end
return res, 2
end
function vararg (...) return {n = select('#', ...), ...} end
local call = function (f, args) return f(table.unpack(args, 1, args.n)) end
assert(f() == 0)
assert(f({1,2,3}, 1, 2, 3) == 3)
assert(f({"alo", nil, 45, f, nil}, "alo", nil, 45, f, nil) == 5)
assert(c12(1,2)==55)
a,b = assert(call(c12, {1,2}))
assert(a == 55 and b == 2)
a = call(c12, {1,2;n=2})
assert(a == 55 and b == 2)
a = call(c12, {1,2;n=1})
assert(not a)
assert(c12(1,2,3) == false)
local a = vararg(call(next, {_G,nil;n=2}))
local b,c = next(_G)
assert(a[1] == b and a[2] == c and a.n == 2)
a = vararg(call(call, {c12, {1,2}}))
assert(a.n == 2 and a[1] == 55 and a[2] == 2)
a = call(print, {'+'})
assert(a == nil)
local t = {1, 10}
function t:f (...) local arg = {...}; return self[...]+#arg end
assert(t:f(1,4) == 3 and t:f(2) == 11)
print('+')
lim = 20
local i, a = 1, {}
while i <= lim do a[i] = i+0.3; i=i+1 end
function f(a, b, c, d, ...)
local more = {...}
assert(a == 1.3 and more[1] == 5.3 and
more[lim-4] == lim+0.3 and not more[lim-3])
end
function g(a,b,c)
assert(a == 1.3 and b == 2.3 and c == 3.3)
end
call(f, a)
call(g, a)
a = {}
i = 1
while i <= lim do a[i] = i; i=i+1 end
assert(call(math.max, a) == lim)
print("+")
-- new-style varargs
function oneless (a, ...) return ... end
function f (n, a, ...)
local b
assert(arg == _G.arg) -- no local 'arg'
if n == 0 then
local b, c, d = ...
return a, b, c, d, oneless(oneless(oneless(...)))
else
n, b, a = n-1, ..., a
assert(b == ...)
return f(n, a, ...)
end
end
a,b,c,d,e = assert(f(10,5,4,3,2,1))
assert(a==5 and b==4 and c==3 and d==2 and e==1)
a,b,c,d,e = f(4)
assert(a==nil and b==nil and c==nil and d==nil and e==nil)
-- varargs for main chunks
f = load[[ return {...} ]]
x = f(2,3)
assert(x[1] == 2 and x[2] == 3 and x[3] == nil)
f = load[[
local x = {...}
for i=1,select('#', ...) do assert(x[i] == select(i, ...)) end
assert(x[select('#', ...)+1] == nil)
return true
]]
assert(f("a", "b", nil, {}, assert))
assert(f())
a = {select(3, table.unpack{10,20,30,40})}
assert(#a == 2 and a[1] == 30 and a[2] == 40)
a = {select(1)}
assert(next(a) == nil)
a = {select(-1, 3, 5, 7)}
assert(a[1] == 7 and a[2] == nil)
a = {select(-2, 3, 5, 7)}
assert(a[1] == 5 and a[2] == 7 and a[3] == nil)
pcall(select, 10000)
pcall(select, -10000)
-- bug in 5.2.2
function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10,
p11, p12, p13, p14, p15, p16, p17, p18, p19, p20,
p21, p22, p23, p24, p25, p26, p27, p28, p29, p30,
p31, p32, p33, p34, p35, p36, p37, p38, p39, p40,
p41, p42, p43, p44, p45, p46, p48, p49, p50, ...)
local a1,a2,a3,a4,a5,a6,a7
local a8,a9,a10,a11,a12,a13,a14
end
-- assertion fail here
f()
print('OK')

@ -0,0 +1,152 @@
-- $Id: verybig.lua,v 1.25 2016/11/07 13:11:28 roberto Exp $
-- See Copyright Notice in file all.lua
print "testing RK"
-- testing opcodes with RK arguments larger than K limit
local function foo ()
local dummy = {
-- fill first 256 entries in table of constants
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112,
113, 114, 115, 116, 117, 118, 119, 120,
121, 122, 123, 124, 125, 126, 127, 128,
129, 130, 131, 132, 133, 134, 135, 136,
137, 138, 139, 140, 141, 142, 143, 144,
145, 146, 147, 148, 149, 150, 151, 152,
153, 154, 155, 156, 157, 158, 159, 160,
161, 162, 163, 164, 165, 166, 167, 168,
169, 170, 171, 172, 173, 174, 175, 176,
177, 178, 179, 180, 181, 182, 183, 184,
185, 186, 187, 188, 189, 190, 191, 192,
193, 194, 195, 196, 197, 198, 199, 200,
201, 202, 203, 204, 205, 206, 207, 208,
209, 210, 211, 212, 213, 214, 215, 216,
217, 218, 219, 220, 221, 222, 223, 224,
225, 226, 227, 228, 229, 230, 231, 232,
233, 234, 235, 236, 237, 238, 239, 240,
241, 242, 243, 244, 245, 246, 247, 248,
249, 250, 251, 252, 253, 254, 255, 256,
}
assert(24.5 + 0.6 == 25.1)
local t = {foo = function (self, x) return x + self.x end, x = 10}
t.t = t
assert(t:foo(1.5) == 11.5)
assert(t.t:foo(0.5) == 10.5) -- bug in 5.2 alpha
assert(24.3 == 24.3)
assert((function () return t.x end)() == 10)
end
foo()
foo = nil
if _soft then return 10 end
print "testing large programs (>64k)"
-- template to create a very big test file
prog = [[$
local a,b
b = {$1$
b30009 = 65534,
b30010 = 65535,
b30011 = 65536,
b30012 = 65537,
b30013 = 16777214,
b30014 = 16777215,
b30015 = 16777216,
b30016 = 16777217,
b30017 = 0x7fffff,
b30018 = -0x7fffff,
b30019 = 0x1ffffff,
b30020 = -0x1ffffd,
b30021 = -65534,
b30022 = -65535,
b30023 = -65536,
b30024 = -0xffffff,
b30025 = 15012.5,
$2$
};
assert(b.a50008 == 25004 and b["a11"] == -5.5)
assert(b.a33007 == -16503.5 and b.a50009 == -25004.5)
assert(b["b"..30024] == -0xffffff)
function b:xxx (a,b) return a+b end
assert(b:xxx(10, 12) == 22) -- pushself with non-constant index
b.xxx = nil
s = 0; n=0
for a,b in pairs(b) do s=s+b; n=n+1 end
-- with 32-bit floats, exact value of 's' depends on summation order
assert(81800000.0 < s and s < 81860000 and n == 70001)
a = nil; b = nil
print'+'
function f(x) b=x end
a = f{$3$} or 10
assert(a==10)
assert(b[1] == "a10" and b[2] == 5 and b[#b-1] == "a50009")
function xxxx (x) return b[x] end
assert(xxxx(3) == "a11")
a = nil; b=nil
xxxx = nil
return 10
]]
-- functions to fill in the $n$
local function sig (x)
return (x % 2 == 0) and '' or '-'
end
F = {
function () -- $1$
for i=10,50009 do
io.write('a', i, ' = ', sig(i), 5+((i-10)/2), ',\n')
end
end,
function () -- $2$
for i=30026,50009 do
io.write('b', i, ' = ', sig(i), 15013+((i-30026)/2), ',\n')
end
end,
function () -- $3$
for i=10,50009 do
io.write('"a', i, '", ', sig(i), 5+((i-10)/2), ',\n')
end
end,
}
file = os.tmpname()
io.output(file)
for s in string.gmatch(prog, "$([^$]+)") do
local n = tonumber(s)
if not n then io.write(s) else F[n]() end
end
io.close()
result = dofile(file)
assert(os.remove(file))
print'OK'
return result

@ -0,0 +1,188 @@
assertTrue (0, 'Zero should coerce to true.')
assertTrue (1, 'Positive number should coerce to true.')
assertTrue (-1, 'Negative number should coerce to true.')
assertTrue ('Test', 'String should coerce to true.')
assertTrue ('', 'Empty string should coerce to true.')
assertTrue (0 + '123' == 123, 'Integer strings should coerce to integers')
assertTrue (0 + '123.45' == 123.45, 'Floating point strings should coerce to floats')
assertTrue (0 + '0xa' == 10, 'Hexidecimal syntax strings should coerce to decimal integers')
assertTrue (0 + '0xa.2' == 10.125, 'Floating point hexidecimal syntax strings should coerce to decimal floats')
assertTrue (0 + '0123' == 123, 'JS Octal syntax strings should be coerced as normal decimal strings in Lua')
assertTrue (0 + '-123' == -123, 'Negative integer strings should coerce to negative integers')
assertTrue (0 + '-0xa.2' == -10.125, 'Negative floating point hexidecimal syntax strings should coerce to negative decimal floats')
assertTrue (0 + 'inf' == math.huge, '"inf" should coerce to inf')
assertTrue (0 + '-inf' == -math.huge, '"-inf" should coerce to negative inf')
local a = 0 + 'nan'
assertTrue (a ~= a, '"nan" should coerce to nan')
assertTrue (not (nil), 'Nil should coerce to false.')
assertTrue (not (false), 'False should be false.')
assertTrue (not (10 == '10'), 'String should coerce to number.')
-- TYPE ERRORS
function conc (a, b)
return a..b
end
a = pcall (conc, 'a', 'b')
b = pcall (conc, 'a', 44)
c = pcall (conc, 55, 'b')
d = pcall (conc, 55, 44)
e = pcall (conc, 'a', {})
f = pcall (conc, {}, 'b')
g = pcall (conc, 'a', os.date)
assertTrue (a, 'Concatenation should not error with two strings')
assertTrue (b, 'Concatenation should not error with a string and a number')
assertTrue (c, 'Concatenation should not error with a number and a string')
assertTrue (d, 'Concatenation should not error with two numbers')
assertTrue (not (e), 'Concatenation should error with a string and a table')
assertTrue (not (f), 'Concatenation should error with a table and a string')
assertTrue (not (g), 'Concatenation should error with a string and a function')
function add (a, b)
return a + b
end
a = pcall (add, 'a', 'b')
b = pcall (add, 'a', 44)
c = pcall (add, 55, 'b')
d = pcall (add, 55, 44)
e = pcall (add, 'a', {})
f = pcall (add, {}, 'b')
g = pcall (add, 'a', os.date)
assertTrue (not (a), 'Addition operator should error with two strings')
assertTrue (not (b), 'Addition operator should error with a string and a number')
assertTrue (not (c), 'Addition operator should error with a number and a string')
assertTrue (d, 'Addition operator should not error with two numbers')
assertTrue (not (e), 'Addition operator should error with a string and a table')
assertTrue (not (f), 'Addition operator should error with a table and a string')
assertTrue (not (g), 'Addition operator should error with a string and a function')
function sub (a, b)
return a - b
end
a = pcall (sub, 'a', 'b')
b = pcall (sub, 'a', 44)
c = pcall (sub, 55, 'b')
d = pcall (sub, 55, 44)
e = pcall (sub, 'a', {})
f = pcall (sub, {}, 'b')
g = pcall (sub, 'a', os.date)
assertTrue (not (a), 'Subtraction operator should error with two strings')
assertTrue (not (b), 'Subtraction operator should error with a string and a number')
assertTrue (not (c), 'Subtraction operator should error with a number and a string')
assertTrue (d, 'Subtraction operator should not error with two numbers')
assertTrue (not (e), 'Subtraction operator should error with a string and a table')
assertTrue (not (f), 'Subtraction operator should error with a table and a string')
assertTrue (not (g), 'Subtraction operator should error with a string and a function')
function mult (a, b)
return a * b
end
a = pcall (mult, 'a', 'b')
b = pcall (mult, 'a', 44)
c = pcall (mult, 55, 'b')
d = pcall (mult, 55, 44)
e = pcall (mult, 'a', {})
f = pcall (mult, {}, 'b')
g = pcall (mult, 'a', os.date)
assertTrue (not (a), 'Multiplication operator should error with two strings')
assertTrue (not (b), 'Multiplication operator should error with a string and a number')
assertTrue (not (c), 'Multiplication operator should error with a number and a string')
assertTrue (d, 'Multiplication operator should not error with two numbers')
assertTrue (not (e), 'Multiplication operator should error with a string and a table')
assertTrue (not (f), 'Multiplication operator should error with a table and a string')
assertTrue (not (g), 'Multiplication operator should error with a string and a function')
function divide (a, b)
return a / b
end
a = pcall (divide, 'a', 'b')
b = pcall (divide, 'a', 44)
c = pcall (divide, 55, 'b')
d = pcall (divide, 55, 44)
e = pcall (divide, 'a', {})
f = pcall (divide, {}, 'b')
g = pcall (divide, 'a', os.date)
assertTrue (not (a), 'Division operator should error with two strings')
assertTrue (not (b), 'Division operator should error with a string and a number')
assertTrue (not (c), 'Division operator should error with a number and a string')
assertTrue (d, 'Division operator should not error with two numbers')
assertTrue (not (e), 'Division operator should error with a string and a table')
assertTrue (not (f), 'Division operator should error with a table and a string')
assertTrue (not (g), 'Division operator should error with a string and a function')
function modu (a, b)
return a % b
end
a = pcall (modu, 'a', 'b')
b = pcall (modu, 'a', 44)
c = pcall (modu, 55, 'b')
d = pcall (modu, 55, 44)
e = pcall (modu, 'a', {})
f = pcall (modu, {}, 'b')
g = pcall (modu, 'a', os.date)
assertTrue (not (a), 'Modulo operator should error with two strings')
assertTrue (not (b), 'Modulo operator should error with a string and a number')
assertTrue (not (c), 'Modulo operator should error with a number and a string')
assertTrue (d, 'Modulo operator should not error with two numbers')
assertTrue (not (e), 'Modulo operator should error with a string and a table')
assertTrue (not (f), 'Modulo operator should error with a table and a string')
assertTrue (not (g), 'Modulo operator should error with a string and a function')
function power (a, b)
return a ^ b
end
a = pcall (power, 'a', 'b')
b = pcall (power, 'a', 44)
c = pcall (power, 55, 'b')
d = pcall (power, 55, 44)
e = pcall (power, 'a', {})
f = pcall (power, {}, 'b')
g = pcall (power, 'a', os.date)
assertTrue (not (a), 'Exponentiation operator should error with two strings')
assertTrue (not (b), 'Exponentiation operator should error with a string and a number')
assertTrue (not (c), 'Exponentiation operator should error with a number and a string')
assertTrue (d, 'Exponentiation operator should not error with two numbers')
assertTrue (not (e), 'Exponentiation operator should error with a string and a table')
assertTrue (not (f), 'Exponentiation operator should error with a table and a string')
assertTrue (not (g), 'Exponentiation operator should error with a string and a function')
function neg (a)
return -a
end
a = pcall (neg, 'a')
b = pcall (neg, 55)
c = pcall (neg, {})
assertTrue (not (a), 'Negation operator should error when passed a string')
assertTrue (b, 'Negation operator should not error when passed a number')
assertTrue (not (c), 'Negation operator should error when passed a table')

@ -0,0 +1,294 @@
local a, b, i = 0, 0, 0
for i = 1, 5 do
a = a + 1
b = b + i
end
assertTrue (a == 5, 'For loop should iterate the correct number of times')
assertTrue (b == 15, 'For loop variable should hold the value of the current iteration')
local funcs = {}
local b = ''
for i = 1, 3 do
table.insert(funcs, function () b = b..i end)
end
funcs[1]()
funcs[2]()
funcs[3]()
assertTrue (b == '123', 'Each loop of numeric for should maintain its own scope')
a = { a = 1, b = 2 }
b = 0
for _ in pairs(a) do b = b + 1 end
assertTrue (b == 2, 'For block should iterate over all properties of a table')
a.a = nil
b = 0
for _ in pairs(a) do b = b + 1 end
assertTrue (b == 1, 'Setting a table property to nil should remove that property from the table.')
b = {}
for i = 1, 3 do
local c = i
b[i] = function() return c end
end
assertTrue (b[1]() == 1, 'Local within a closure should keep its value [1]')
assertTrue (b[2]() == 2, 'Local within a closure should keep its value [2]')
assertTrue (b[3]() == 3, 'Local within a closure should keep its value [3]')
b = ''
i = 1
for i = 1, 4 do
b = b..i
break
end
assertTrue (b == '1', 'Break should exit numerical for loop')
a = ''
u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau', [function () end] = 'test'}
for key, val in pairs(u) do
a = a..'['..tostring(key)..'=='..tostring(val)..']'
end
assertTrue (string.find(a, '[6.28==tau]') ~= nil, 'for/pairs iteration should include items with double as key.')
assertTrue (string.find(a, '[@!#==qbert]') ~= nil, 'for/pairs iteration should include items with string as key.')
assertTrue (string.find(a, '[table: 0x%d+==1729]') ~= nil, 'for/pairs iteration should include items with table as key.')
assertTrue (string.find(a, '[function: 0x%d+==test]') ~= nil, 'for/pairs iteration should include items with function as key.')
a = ''
t = {1,2,3}
for key, val in pairs(t) do
a = a..'['..tostring(key)..'=='..tostring(val)..']'
break
end
assertTrue (a == '[1==1]', 'Break should exit generic for loop')
function iter(t, i)
if i < 5 then
return i + 1, i
else
return nil
end
end
a = ''
for key, val in iter, {}, 2 do
a = a..'['..tostring(key)..'=='..tostring(val)..']'
end
assertTrue (a == '[3==2][4==3][5==4]', 'Generic for loop should accept an expression list')
function iter(t, i)
i = i + 1
v = t[i]
if v then
return i, v, v * 2
end
end
a = ''
for x,y,z in iter, {4,5,6}, 0 do
a = a..'['..tostring(x)..','..tostring(y)..','..tostring(z)..']'
end
assertTrue (a == '[1,4,8][2,5,10][3,6,12]', 'Generic for loop should pass all values returned from iterator to the variables in the loop')
local funcs = {}
local a = {1,2,3}
local b = ''
for _, i in ipairs(a) do
table.insert(funcs, function () b = b..i end)
end
funcs[1]()
funcs[2]()
funcs[3]()
assertTrue (b == '123', 'Each loop of generic for should maintain its own scope')
a = ''
b = 1
while #a < 15 do
a = a..b
b = b + 1
end
assertEqual (a, '123456789101112', 'While loop should iterate until condition is met')
local fail = false
while b < 0 do
fail = true
end
assertEqual (fail, false, 'While loop should not iterate if condition is never met')
b = {}
i = 1
while i < 4 do
local c = i
b[i] = function() return c end
i = i + 1
end
assertTrue (b[1]() == 1, 'Local within a while loop closure should keep its value [1]')
assertTrue (b[2]() == 2, 'Local within a while loop closure should keep its value [2]')
assertTrue (b[3]() == 3, 'Local within a while loop closure should keep its value [3]')
b = ''
i = 1
while i < 4 do
b = b..i
i = i + 1
break
end
assertTrue (b == '1', 'Break should exit while loop')
local funcs = {}
local b = ''
i = 1
while i < 4 do
table.insert(funcs, function () b = b..i end)
i = i + 1
end
funcs[1]()
funcs[2]()
funcs[3]()
assertTrue (b == '444', 'Each loop of while should not maintain its own scope')
a = ''
b = 1
repeat
a = a..b
b = b + 1
until #a > 15
assertEqual (a, '12345678910111213', 'Repeat loop should iterate until condition is met')
b = 1
repeat
b = b + 1
until b > 0
assertEqual (b, 2, 'Repeat loop should iterate once if condition is always met')
b = {}
i = 1
repeat
local c = i
b[i] = function() return c end
i = i + 1
until i == 4
assertTrue (b[1]() == 1, 'Local within a while loop closure should keep its value [1]')
assertTrue (b[2]() == 2, 'Local within a while loop closure should keep its value [2]')
assertTrue (b[3]() == 3, 'Local within a while loop closure should keep its value [3]')
b = ''
i = 1
repeat
b = b..i
i = i + 1
break
until i == 4
assertTrue (b == '1', 'Break should exit repeat loop')
local funcs = {}
local b = ''
i = 1
repeat
table.insert(funcs, function () b = b..i end)
i = i + 1
until i == 4
funcs[1]()
funcs[2]()
funcs[3]()
assertTrue (b == '444', 'Each loop of repeat should not maintain its own scope')
a = ':'
t = { 123, 456, x = 789, 10 }
for i, v in ipairs(t) do
a = a..i..'='..v..':'
end
assertTrue (string.find(a, ':1=123:') ~= nil, 'for-in-ipairs loop should iterate over numeric keys [1]')
assertTrue (string.find(a, ':2=456:') ~= nil, 'for-in-ipairs loop should iterate over numeric keys [2]')
assertTrue (string.find(a, ':3=10:') ~= nil, 'for-in-ipairs loop should iterate over numeric keys [3]')
assertTrue (string.find(a, ':x=789:') == nil, 'for-in-ipairs loop should not iterate over non-numeric keys')
assertEqual (a, ':1=123:2=456:3=10:', 'for-in-ipairs loop should iterate over numeric keys in order')
b = nil
a = function ()
return false, true
end
if a() then
b = 'THIS SHOULD NOT EXECUTE'
end
assertEqual (b, nil, 'If clause with function call should only use first returned value. [1]')
b = nil
a = function ()
return true, false
end
if a() then
b = 'THIS SHOULD EXECUTE'
end
assertEqual (b, 'THIS SHOULD EXECUTE', 'If clause with function call should only use first returned value. [2]')
-- do block
local a = 1
local b = 10
do
local a = 2
b = 20
assertEqual (a, 2, 'Do block should use local variables')
assertEqual (b, 20, 'Do block should set upvalues')
end
assertEqual (a, 1, 'Do block should create its own local variable scope')
assertEqual (b, 20, 'Do block should update upvalues')
a = '1'
function testReturn()
a = a..'2'
do
a = a..'3'
return
end
a = a..'X'
end
testReturn()
assertEqual (a, '123', 'Do block containing return should return from parent function')

@ -0,0 +1,134 @@
local b = 20
function addOne ()
assertTrue (b == 20, 'Functions should be able to access locals of parent closures [1]')
function nested ()
assertTrue (b == 20, 'Functions should be able to access locals of parent closures [2]')
local c = 9
assertTrue (c == 9, 'Functions should be able to access their own locals')
end
nested ()
assertTrue (c == nil, 'Function locals should not be accessible from outside the function')
b = b + 1
assertTrue (b == 21, 'Operations performed on upvalues should use external value')
end
addOne ()
assertTrue (b == 21, 'Operations performed on upvalues in functions should affect the external value too')
function f (...)
local a, b, c = ...
assertTrue (a == -1, 'Varargs should pass values around correctly [1]')
assertTrue (b == 0, 'Varargs should pass values around correctly [2]')
assertTrue (c == 2, 'Varargs should pass values around correctly [3]')
local d, e, f, g, h = ...
assertTrue (d == -1, 'Varargs should pass values around correctly [4]')
assertTrue (e == 0, 'Varargs should pass values around correctly [5]')
assertTrue (f == 2, 'Varargs should pass values around correctly [6]')
assertTrue (g == 9, 'Varargs should pass values around correctly [7]')
assertTrue (h == nil, 'Varargs should pass nil for list entries beyond its length')
end
f(-1,0,2,9)
function g (a, ...)
local b, c = ...
assertTrue (a == -1, 'Varargs should pass values around correctly [8]')
assertTrue (b == 0, 'Varargs should pass values around correctly [9]')
assertTrue (c == 2, 'Varargs should pass values around correctly [10]')
end
g(-1,0,2,9)
function h (a, b, ...)
local c = ...
assertTrue (a == -1, 'Varargs should pass values around correctly [11]')
assertTrue (b == 0, 'Varargs should pass values around correctly [12]')
assertTrue (c == 2, 'Varargs should pass values around correctly [13]')
end
h(-1,0,2,9)
function getFunc ()
local b = 6
return function () return b end
end
x = getFunc () ()
assertTrue (x == 6, 'Functions should be able to return functions (and maintain their scope)')
function add (val1)
return function (val2) return val1 + val2 end
end
local addThree = add (3)
x = addThree (4)
assertTrue (x == 7, 'Functions should be able to be curried')
do
local function x()
return 'inner'
end
function y()
return x()
end
end
function x()
return 'outer'
end
local z = y()
assertTrue (z == 'inner', 'Local functions should be locally scoped')
function oneTwo()
return 1, 2
end
function testReturnValues()
return oneTwo(), 10
end
local a, b, c = testReturnValues()
assertTrue (a == 1, 'return should return the first item returned from a function call')
assertTrue (b == 10, 'return should only return the first item from a function call when not last item in an expression list')
assertTrue (c == nil, 'return should know when to stop')
function testReturnValues2()
return 10, oneTwo()
end
local a, b, c = testReturnValues2()
assertTrue (a == 10, 'return should return the first item in an expression list')
assertTrue (b == 1, 'return should return the first item from a function call')
assertTrue (c == 2, 'return should return all items returned from a function call if at end of expression list')
function testArgs1(a, b, c)
assertTrue (a == 1, 'Function call in argument list should pass return value as argument')
assertTrue (b == 10, 'Function call in middle of argument list should only pass one argument')
assertTrue (c == nil, 'Arguments should stop at the end of argument list')
end
testArgs1(oneTwo(), 10)
function testArgs2(a, b, c)
assertTrue (a == 10, 'Arguments should be passed in order')
assertTrue (b == 1, 'Function call should pass return values')
assertTrue (c == 2, 'Function call at end of argument list should pass all return values')
end
testArgs2(10, oneTwo())

@ -0,0 +1,60 @@
local innerOrder = {'Y','twelve','ten','six','four'}
local midOrder = {'Z','thirteen','nine','seven','three'}
local outerOrder = {'two','eight','fourteen','A'}
local loopOrder = {'five','eleven','fifteen','B'}
local order = ''
local arguments = ''
function innerFunc (...)
order = order..table.remove(innerOrder)
local a, b, c = ...
arguments = arguments..'Ia'..tostring(a)..tostring(b)..tostring(c)
a, b, c = coroutine.yield (...)
order = order..table.remove(innerOrder)
arguments = arguments..'Ib'..tostring(a)..tostring(b)..tostring(c)
end
function midFunc ()
order = order..table.remove(midOrder)
arguments = arguments..'Ma'
innerFunc ('IIaa')
order = order..table.remove(midOrder)
arguments = arguments..'Mb'
end
function outerFunc ()
order = order..outerOrder[1]
arguments = arguments..'Oa'
for i = 2,3 do
midFunc ()
arguments = arguments..'Ob'
order = order..outerOrder[i]
end
end
order = order..'one'
co = coroutine.create (outerFunc)
for f = 1, 3 do
local x, y, z = coroutine.resume (co, 123)
order = order..loopOrder[f]
arguments = arguments..'loop'..tostring(x)..tostring(y)..tostring(z)
end
order = order..'sixteen'
assertTrue (order == 'onetwothreefourfivesixseveneightnineteneleventwelvethirteenfourteenfifteensixteen', 'Coroutines should execute in the correct order')
assertTrue (arguments == 'OaMaIaIIaanilnillooptrueIIaanilIb123nilnilMbObMaIaIIaanilnillooptrueIIaanilIb123nilnilMbOblooptruenilnil', 'Coroutines should pass the correct values to and from yields and resumes')

File diff suppressed because it is too large Load Diff

@ -0,0 +1,18 @@
-- Implicit in the fact that it's already using assertTrue()
assertTrue (mainGlobal2 == 'mainGlbl', 'Files loaded by loadfile() should have access to the same global namespace')
assertTrue (mainLocal == nil, 'Files loaded by loadfile() should not have access to the local scope of the caller')
local testModName = ...
assertTrue (testModName == nil, 'Files loaded by loadfile() should not be passed any values in varargs.')
mainGlobal1 = 'innerGlbl'
local innerLocal = 'innerLoc'
return {
getValue = function ()
return 'moo'
end
}

@ -0,0 +1,168 @@
-- abs
local a = math.abs (10)
local b = math.abs (-20)
local c = math.abs (2.56)
local d = math.abs (-34.67)
local e = math.abs (-0)
assertTrue (a == 10, 'math.abs() should return the passed argument if it is positive')
assertTrue (b == 20, 'math.abs() should return the positive form of the passed argument if it is negative')
assertTrue (c == 2.56, 'math.abs() should return the passed argument if it is a positive floating point number')
assertTrue (d == 34.67, 'math.abs() should return the positive form of the passed argument if it is a positive floating point number')
assertTrue (e == 0, 'math.abs() should return zero if passed zero')
-- math.acos
-- math.cos
local a = math.acos (1)
--local b = math.acos (math.cos (0.3))
local c = math.cos (0)
--local d = math.cos (math.acos (0.3))
assertTrue (a == 0, 'math.acos() should return 0 when passed 1')
--assertTrue (b == 0.3, 'math.acos() should return x when passed math.cos(x)')
assertTrue (c == 1, 'math.cos() should return 1 when passed 0')
--assertTrue (d == 0.3, 'math.cos() should return x when passed math.acos(x)')
-- math.asin
-- math.sin
local a = math.asin (0)
--local b = math.asin (math.sin (90))
local c = math.sin (0)
local d = math.sin (math.asin (0.2))
assertTrue (a == 0, 'math.asin() should return 0 when passed 0')
--assertTrue (b == 90, 'math.asin() should return x when passed math.sin(x)')
assertTrue (c == 0, 'math.sin() should return 0 when passed 0')
assertTrue (d == 0.2, 'math.sin() should return x when passed math.asin(x)')
-- math.atan
-- math.tan
local a = math.atan (0)
--local b = math.atan (math.tan (0.3))
local c = math.tan (0)
local d = math.tan (math.atan (0.3))
assertTrue (a == 0, 'math.atan() should return 0 when passed 0')
--assertTrue (b == 0.3, 'math.atan() should return x when passed math.tan(x)')
assertTrue (c == 0, 'math.tan() should return 0 when passed 0')
assertTrue (d == 0.3, 'math.tan() should return x when passed math.atan(x)')
-- math.ceil
local a = math.ceil (14)
local b = math.ceil (14.45)
local c = math.ceil (14.5)
local d = math.ceil (0.1)
local e = math.ceil (0.6)
local f = math.ceil (-0.6)
local g = math.ceil (-122.4)
assertTrue (a == 14, 'math.ceil() should round up to the next integer [1]')
assertTrue (b == 15, 'math.ceil() should round up to the next integer [2]')
assertTrue (c == 15, 'math.ceil() should round up to the next integer [3]')
assertTrue (d == 1, 'math.ceil() should round up to the next integer [4]')
assertTrue (e == 1, 'math.ceil() should round up to the next integer [5]')
assertTrue (f == 0, 'math.ceil() should round up to the next integer [6]')
assertTrue (g == -122, 'math.ceil() should round up to the next integer [7]')
-- math.deg
a = math.deg (0)
b = math.deg (math.pi)
c = math.deg (math.pi * 2)
d = math.deg (math.pi / 2)
assertTrue (a == 0, 'math.deg() should return 0 when passed zero')
assertTrue (b == 180, 'math.deg() should return 180 when passed Pi')
assertTrue (c == 360, 'math.deg() should return 360 when passed 2Pi')
assertTrue (d == 90, 'math.deg() should return 90 when passed Pi/2')
--math.frexp
a, b = math.frexp(63)
assertTrue (a == 0.984375, 'math.frexp should return the correct mantissa when passed a positive number.')
assertTrue (b == 6, 'math.frexp should return the correct exponent when passed a positive number.')
a, b = math.frexp(-63)
assertTrue (a == -0.984375, 'math.frexp should return the correct mantissa when passed a negative number.')
assertTrue (b == 6, 'math.frexp should return the correct exponent when passed a negative number.')
a, b = math.frexp(0)
assertTrue (a == 0, 'math.frexp should return a zero mantissa when passed zero.')
assertTrue (b == 0, 'math.frexp should return a zero exponent when passed zero.')
--math.huge
a = math.huge + 1
b = -math.huge - 1
assertTrue (a == math.huge, 'math.huge should not change value with addition.')
assertTrue (b == -math.huge, 'Negative math.huge should not change value with subtraction.')
-- math.rad
a = math.rad (0)
b = math.rad (180)
c = math.rad (270)
d = math.rad (360)
e = math.rad (450)
f = math.rad (-180)
assertTrue (a == 0, 'math.rad() should return 0 when passed zero')
assertTrue (b == math.pi, 'math.rad() should return Pi when passed 180')
assertTrue (c == 1.5 * math.pi, 'math.rad() should return 1.5*Pi when passed 270')
assertTrue (d == 2 * math.pi, 'math.rad() should return 2*Pi when passed 360')
assertTrue (e == 2.5 * math.pi, 'math.rad() should return 2.5*Pi when passed 450')
assertTrue (f == -math.pi, 'math.rad() should return -Pi when passed -180')
-- math.random
a = math.random()
b = math.random()
assertTrue (a == 16807 / 2147483647, 'math.random() should initialise with a value of 1')
assertTrue (b == ((16807 * a * 2147483647) % 2147483647) / 2147483647, 'math.random() should follow the right sequence [1]')
-- math.randomseed
math.randomseed(123)
c = math.random()
d = math.random()
assertTrue (c == ((16807 * 123) % 2147483647) / 2147483647, 'math.random() should follow the right sequence [2]')
assertTrue (d == ((16807 * c * 2147483647) % 2147483647) / 2147483647, 'math.random() should follow the right sequence [3]')

@ -0,0 +1,26 @@
-- Implicit in the fact that it's already using assertTrue()
assertTrue (mainGlobal2 == 'mainGlbl', 'Modules should have access to the same global namespace')
assertTrue (mainLocal == nil, 'Modules should not have access to the local scope of the caller')
local testModName = ...
assertTrue (testModName == 'lib-require', 'A module\'s name should be passed into the module using varargs.')
local sub = require 'lib-require.sub-module' -- test dot syntax
assertTrue(type(sub) == 'table', 'Module should be able to load more modules using dot syntax.')
local sub2 = require 'lib-require/sub-module' -- test slash syntax
assertTrue(type(sub2) == 'table', 'Module should be able to load more modules using slash syntax.')
mainGlobal1 = 'innerGlbl'
local innerLocal = 'innerLoc'
moduleInitCount = moduleInitCount + 1
return {
getValue = function ()
return 'modVal'
end
}

@ -0,0 +1,616 @@
-- byte
local a, b = string.byte ('Mo0')
assertTrue (a == 77, 'string.byte() should return the numerical code for the first character in the first returned item')
assertTrue (b == nil, 'string.byte() should return only one item when no length is given [1]')
local a, b = string.byte ('Mo0', 2)
assertTrue (a == 111, 'string.byte() should return the numerical code for the nth character in the first returned item, when n is specified in the second argument [1]')
assertTrue (b == nil, 'string.byte() should return only one item when no length is given [2]')
local a, b, c = string.byte ('Mo0', 2, 3)
assertTrue (a == 111, 'string.byte() should return the numerical code for the nth character in the first returned item, when n is specified in the second argument [2]')
assertTrue (b == 48, 'string.byte() should return the numerical code for the nth character in the first returned item, when n is specified in the second argument [3]')
assertTrue (c == nil, 'string.byte() should return only the number of items specified in the length argument or the up to the end of the string, whichever is encountered first [1]')
local a, b, c = string.byte ('Mo0', 3, 20)
assertTrue (a == 48, 'string.byte() should return the numerical code for the nth character in the first returned item, when n is specified in the second argument [4]')
assertTrue (b == nil, 'string.byte() should return only the number of items specified in the length argument or the up to the end of the string, whichever is encountered first [2]')
-- char
local a = string.char ()
local b = string.char (116, 101, 115, 116, 105, 99, 108, 101, 115)
assertTrue (a == '', 'string.byte() should return an empty string when called with no arguments')
assertTrue (b == 'testicles', 'string.byte() should return a string comprising of characters representing by the value each of the arguments passed')
-- -- dump
-- local f = function () end
-- local a = string.dump(f)
-- assertTrue (type(a) == 'string', 'string.dump() should return a string when called with a function')
-- local s = string.dump(function () return 'bar' end)
-- f = load(s)
-- assertTrue (type(f) == 'function', 'load() should create a function from the output of string.dump() [1]')
-- result = f()
-- assertTrue (result == 'bar', 'The result of load(string.dump(f)) should behave the same as f() [1]')
-- function namedFuncWithParams (a, b)
-- return a..b
-- end
-- s = string.dump(namedFuncWithParams)
-- f = load(s)
-- assertTrue (type(f) == 'function', 'load() should create a function from the output of string.dump() [2]')
-- result = f('hel','lo')
-- assertTrue (result == 'hello', 'The result of load(string.dump(f)) should behave the same as f() [2]')
-- find
local a = 'The quick brown fox'
local b = string.find (a, 'quick');
local c = string.find (a, 'fox');
local d = string.find (a, 'kipper');
local e = string.find (a, '');
local f = string.find (a, 'quick', 8);
local g = string.find (a, 'fox', 8);
assertTrue (b == 5, 'string.find() should return the location of the first occurrence of the second argument within the first, if it is present [1]')
assertTrue (c == 17, 'string.find() should return the location of the first occurrence of the second argument within the first, if it is present [2]')
assertTrue (d == nil, 'string.find() should return nil if the second argument is not contained within the first [1]')
assertTrue (e == 1, 'string.find() should return return 1 if the second argument is an empty string')
assertTrue (f == nil, 'string.find() should return nil if the second argument is not contained within the first after the index specified by the third argument')
assertTrue (g == 17, 'string.find() should return the location of the second argument if it is contained within the first after the index specified by the third argument')
local b, c, d, e = string.find (a, 'q(.)(.)');
assertEqual (b, 5, 'string.find() should return the location of the first occurrence of the second argument within the first, if it is present [3]')
assertEqual (c, 7, 'string.find() should return the location of the last character of the first occurrence of the second argument within the first, if it is present')
assertEqual (d, 'u', 'string.find() should return the groups that are specified in the regex. [1]')
assertEqual (e, 'i', 'string.find() should return the groups that are specified in the regex. [2]')
b = string.find('[', '[_%w]')
assertTrue (b == nil, 'string.find() should not return the location of special syntax [ and ].')
local a = [[The quick
brown fox]]
b = string.find(a, 'The .* fox')
assertTrue (b == 1, 'The dot pattern should match across lines in string.find()')
-- format
do
local a = string.format("%s %q", "Hello", "Lua user!")
local b = string.format("%c%c%c", 76,117,97) -- char
local c = string.format("%e, %E", math.pi,math.pi) -- exponent
local d1 = string.format("%f", math.pi) -- float
local d2 = string.format("%g", math.pi) -- compact float
-- issues:
local e = string.format("%d, %i, %u", -100,-100,-100) -- signed, signed, unsigned integer
local f = string.format("%o, %x, %X", -100,-100,-100) -- octal, hex, hex
local g = string.format("%%s", 100)
assertTrue (a == 'Hello "Lua user!"', 'string.format() should format %s and %q correctly')
assertTrue (b == 'Lua', 'string.format() should format %c correctly')
assertTrue (d1 == '3.141593', 'string.format() should format %f correctly')
assertTrue (e == '-100, -100, 4294967196', 'string.format() should format %d, %i and %u correctly')
assertTrue (f == '37777777634, ffffff9c, FFFFFF9C', 'string.format() should format %o, %x and %X correctly')
-- 64bit required
-- assertTrue (e == '-100, -100, 18446744073709551516', 'string.format() should format %d, %i and %u correctly')
-- assertTrue (f == '1777777777777777777634, ffffffffffffff9c, FFFFFFFFFFFFFF9C', 'string.format() should format %o, %x and %X correctly')
assertTrue (g == '%s', 'string.format() should format %% correctly')
assertTrue (c == '3.141593e+00, 3.141593E+00', 'string.format() should format %e and %E correctly')
assertTrue (d2 == '3.14159', 'string.format() should format %g correctly')
a = function () string.format("%*", 100) end
b = function () string.format("%l", 100) end
c = function () string.format("%L", 100) end
d = function () string.format("%n", 100) end
e = function () string.format("%p", 100) end
f = function () string.format("%h", 100) end
assertTrue (not pcall(a), 'string.format() should error when passed %*')
assertTrue (not pcall(b), 'string.format() should error when passed %l')
assertTrue (not pcall(c), 'string.format() should error when passed %L')
assertTrue (not pcall(d), 'string.format() should error when passed %n')
assertTrue (not pcall(e), 'string.format() should error when passed %p')
assertTrue (not pcall(f), 'string.format() should error when passed %h')
a = string.format("%.3f", 5.3)
b = "Lua version " .. string.format("%.1f", 5.3)
c = string.format("pi = %.4f", math.pi)
f = string.format("%.3f", 5)
local d, m, y = 5, 11, 1990
e = string.format("%02d/%02d/%04d", d, m, y)
assertTrue (a == '5.300', 'string.format() should format floating point numbers correctly[1]')
assertTrue (b == 'Lua version 5.3', 'string.format() should format floating point numbers correctly[2]')
assertTrue (c == 'pi = 3.1416', 'string.format() should format floating point numbers correctly[3]')
assertTrue (e == '05/11/1990', 'string.format() should format decimals correctly [0]')
assertTrue (f == '5.000', 'string.format() should format floating point numbers correctly[4]')
a = function () string.format('%#####s', 'x') end
b = function () string.format('%######s', 'x') end
assertTrue (pcall(a), 'string.format() should handle five flags')
assertTrue (not pcall(b), 'string.format() should not handle six flags')
local tag, title = "h1", "a title"
a = string.format("<%s>%s</%s>", tag, title, tag)
b = string.format("%8s", "Lua")
c = string.format("%.8s", "Lua")
d = string.format("%.2s", "Lua")
e = string.format("%8.2s", "Lua")
f = string.format("%+8.2s", "Lua")
g = string.format("%-8.2s", "Lua")
local h = string.format("%08.2s", "Lua")
local i = string.format("%#8.2s", "Lua")
local j = string.format("% 8.2s", "Lua")
local k = string.format("%+-0# 8.2s", "Lua")
local l = string.format("%0.2s", "Lua")
assertTrue (a == '<h1>a title</h1>', 'string.format() should format strings correctly[1]')
assertTrue (b == ' Lua', 'string.format() should format strings correctly[2]')
assertTrue (c == 'Lua', 'string.format() should format strings correctly[3]')
assertTrue (d == 'Lu', 'string.format() should format strings correctly[4]')
assertTrue (e == ' Lu', 'string.format() should format strings correctly[5]')
assertTrue (f == ' Lu', 'string.format() should format strings correctly[6]')
assertTrue (g == 'Lu ', 'string.format() should format strings correctly[7]')
assertTrue (h == '000000Lu', 'string.format() should format strings correctly[8]')
assertTrue (i == ' Lu', 'string.format() should format strings correctly[9]')
assertTrue (j == ' Lu', 'string.format() should format strings correctly[10]')
assertTrue (k == 'Lu ', 'string.format() should format strings correctly[11]')
assertTrue (l == 'Lu', 'string.format() should format strings correctly[12]')
a = string.format("%8d", 123.45)
b = string.format("%.8d", 123.45)
c = string.format("%.2d", 123.45)
d = string.format("%8.2d", 123.45)
e = string.format("%+8.2d", 123.45)
f = string.format("%-8.2d", 123.45)
g = string.format("%08.2d", 123.45)
h = string.format("%#8.2d", 123.45)
i = string.format("% 8.2d", 123.45)
j = string.format("%+-0# 8.2d", 123.45)
k = string.format("%0.2d", 123.45)
l = string.format("%+.8d", 123.45)
local m = string.format("%-.8d", 123.45)
local n = string.format("%#.8d", 123.45)
local o = string.format("%0.8d", 123.45)
local p = string.format("% .8d", 123.45)
local q = string.format("%+-#0 .8d", 123.45)
local r = string.format("%8.5d", 123.45)
local s = string.format("%+8.5d", 123.45)
local t = string.format("%-8.5d", 123.45)
local u = string.format("%-+8.5d", 123.45)
local v = string.format("%5d", 12.3e8)
local w = string.format("%.d", 123.45)
assertTrue (a == ' 123', 'string.format() should format decimals correctly[1]')
assertTrue (b == '00000123', 'string.format() should format decimals correctly[2]')
assertTrue (c == '123', 'string.format() should format decimals correctly[3]')
assertTrue (d == ' 123', 'string.format() should format decimals correctly[4]')
assertTrue (e == ' +123', 'string.format() should format decimals correctly[5]')
assertTrue (f == '123 ', 'string.format() should format decimals correctly[6]')
assertTrue (g == ' 123', 'string.format() should format decimals correctly[7]')
assertTrue (h == ' 123', 'string.format() should format decimals correctly[8]')
assertTrue (i == ' 123', 'string.format() should format decimals correctly[9]')
assertTrue (j == '+123 ', 'string.format() should format decimals correctly[10]')
assertTrue (k == '123', 'string.format() should format decimals correctly[11]')
assertTrue (l == '+00000123', 'string.format() should format decimals correctly[12]')
assertTrue (m == '00000123', 'string.format() should format decimals correctly[13]')
assertTrue (n == '00000123', 'string.format() should format decimals correctly[14]')
assertTrue (o == '00000123', 'string.format() should format decimals correctly[15]')
assertTrue (p == ' 00000123', 'string.format() should format decimals correctly[16]')
assertTrue (q == '+00000123', 'string.format() should format decimals correctly[17]')
assertTrue (r == ' 00123', 'string.format() should format decimals correctly[18]')
assertTrue (s == ' +00123', 'string.format() should format decimals correctly[19]')
assertTrue (t == '00123 ', 'string.format() should format decimals correctly[20]')
assertTrue (u == '+00123 ', 'string.format() should format decimals correctly[21]')
assertTrue (v == '1230000000', 'string.format() should format decimals correctly[22]')
assertTrue (w == '123', 'string.format() should format decimals correctly[23]')
a = string.format("%8d", -123.45)
b = string.format("%.8d", -123.45)
c = string.format("%.2d", -123.45)
d = string.format("%8.2d", -123.45)
e = string.format("%+8.2d", -123.45)
f = string.format("%-8.2d", -123.45)
g = string.format("%08.2d", -123.45)
h = string.format("%#8.2d", -123.45)
i = string.format("% 8.2d", -123.45)
j = string.format("%+-0# 8.2d", -123.45)
k = string.format("%0.2d", -123.45)
l = string.format("%+.8d", -123.45)
m = string.format("%-.8d", -123.45)
n = string.format("%#.8d", -123.45)
o = string.format("%0.8d", -123.45)
p = string.format("% .8d", -123.45)
q = string.format("%+-#0 .8d", -123.45)
r = string.format("%8.5d", -123.45)
s = string.format("%+8.5d", -123.45)
t = string.format("%-8.5d", -123.45)
u = string.format("%-+8.5d", -123.45)
v = string.format("%5d", -12.3e8)
w = string.format("%.d", -123.45)
assertTrue (a == ' -123', 'string.format() should format decimals correctly[31]')
assertTrue (b == '-00000123', 'string.format() should format decimals correctly[32]')
assertTrue (c == '-123', 'string.format() should format decimals correctly[33]')
assertTrue (d == ' -123', 'string.format() should format decimals correctly[34]')
assertTrue (e == ' -123', 'string.format() should format decimals correctly[35]')
assertTrue (f == '-123 ', 'string.format() should format decimals correctly[36]')
assertTrue (g == ' -123', 'string.format() should format decimals correctly[37]')
assertTrue (h == ' -123', 'string.format() should format decimals correctly[38]')
assertTrue (i == ' -123', 'string.format() should format decimals correctly[39]')
assertTrue (j == '-123 ', 'string.format() should format decimals correctly[40]')
assertTrue (k == '-123', 'string.format() should format decimals correctly[41]')
assertTrue (l == '-00000123', 'string.format() should format decimals correctly[42]')
assertTrue (m == '-00000123', 'string.format() should format decimals correctly[43]')
assertTrue (n == '-00000123', 'string.format() should format decimals correctly[44]')
assertTrue (o == '-00000123', 'string.format() should format decimals correctly[45]')
assertTrue (p == '-00000123', 'string.format() should format decimals correctly[46]')
assertTrue (q == '-00000123', 'string.format() should format decimals correctly[47]')
assertTrue (r == ' -00123', 'string.format() should format decimals correctly[48]')
assertTrue (s == ' -00123', 'string.format() should format decimals correctly[49]')
assertTrue (t == '-00123 ', 'string.format() should format decimals correctly[50]')
assertTrue (u == '-00123 ', 'string.format() should format decimals correctly[51]')
assertTrue (v == '-1230000000', 'string.format() should format decimals correctly[52]')
assertTrue (w == '-123', 'string.format() should format decimals correctly[53]')
a = string.format("%+05.d", 123.45)
b = string.format("%05d", 123.45)
c = string.format("%05d", -123.45)
d = string.format("%+05d", 123.45)
assertTrue (a == ' +123', 'string.format() should format decimals correctly[60]')
assertTrue (b == '00123', 'string.format() should format decimals correctly[61]')
assertTrue (c == '-0123', 'string.format() should format decimals correctly[62]')
assertTrue (d == '+0123', 'string.format() should format decimals correctly[63]')
a = string.format("%8f", 123.45)
b = string.format("%.8f", 123.45)
c = string.format("%.1f", 123.45)
d = string.format("%8.2f", 123.45)
e = string.format("%+8.2f", 123.45)
f = string.format("%-8.3f", 123.45)
g = string.format("%08.3f", 123.45)
h = string.format("%#8.3f", 123.45)
i = string.format("% 8.3f", 123.45)
j = string.format("%+-0# 8.2f", 123.45)
k = string.format("%0.2f", 123.45)
l = string.format("%+.8f", 123.45)
m = string.format("%-.8f", 123.45)
n = string.format("%#.8f", 123.45)
o = string.format("%9.3f", 123.45)
p = string.format("%+9.3f", 123.45)
q = string.format("%-9.3f", 123.45)
r = string.format("%-+9.3f", 123.45)
s = string.format("%.0f", 123.45)
t = string.format("%.4f", 123.05)
assertTrue (a == '123.450000', 'string.format() should format floats correctly[1]')
assertTrue (b == '123.45000000', 'string.format() should format floats correctly[2]')
assertTrue (c == '123.5', 'string.format() should format floats correctly[3]')
assertTrue (d == ' 123.45', 'string.format() should format floats correctly[4]')
assertTrue (e == ' +123.45', 'string.format() should format floats correctly[5]')
assertTrue (f == '123.450 ', 'string.format() should format floats correctly[6]')
assertTrue (g == '0123.450', 'string.format() should format floats correctly[7]')
assertTrue (h == ' 123.450', 'string.format() should format floats correctly[8]')
assertTrue (i == ' 123.450', 'string.format() should format floats correctly[9]')
assertTrue (j == '+123.45 ', 'string.format() should format floats correctly[10]')
assertTrue (k == '123.45', 'string.format() should format floats correctly[11]')
assertTrue (l == '+123.45000000', 'string.format() should format floats correctly[12]')
assertTrue (m == '123.45000000', 'string.format() should format floats correctly[13]')
assertTrue (n == '123.45000000', 'string.format() should format floats correctly[14]')
assertTrue (o == ' 123.450', 'string.format() should format floats correctly[15]')
assertTrue (p == ' +123.450', 'string.format() should format floats correctly[16]')
assertTrue (q == '123.450 ', 'string.format() should format floats correctly[17]')
assertTrue (r == '+123.450 ', 'string.format() should format floats correctly[18]')
assertTrue (s == '123', 'string.format() should format floats correctly[19]')
assertTrue (t == '123.0500', 'string.format() should format floats correctly[20]')
a = string.format("%x", 123)
b = string.format("%x", 123.45)
c = string.format("%x", -123)
d = string.format("%4x", 123)
e = string.format("%.4x", 123)
f = string.format("%8.4x", 123)
g = string.format("%+8.4x", 123)
h = string.format("%-8.4x", 123)
i = string.format("%#8.4x", 123)
j = string.format("%08.4x", 123)
k = string.format("% 8.4x", 123)
l = string.format("%+-#0 8.4x", 123)
m = string.format("%08x", 123)
n = string.format("% x", 123)
assertTrue (a == '7b', 'string.format() should format hex correctly[1]')
assertTrue (b == '7b', 'string.format() should format hex correctly[2]')
assertTrue (c == 'ffffff85', 'string.format() should format hex correctly[3]')
assertTrue (d == ' 7b', 'string.format() should format hex correctly[4]')
assertTrue (e == '007b', 'string.format() should format hex correctly[5]')
assertTrue (f == ' 007b', 'string.format() should format hex correctly[6]')
assertTrue (g == ' 007b', 'string.format() should format hex correctly[7]')
assertTrue (h == '007b ', 'string.format() should format hex correctly[8]')
assertTrue (i == ' 0x007b', 'string.format() should format hex correctly[9]')
assertTrue (k == ' 007b', 'string.format() should format hex correctly[11]')
assertTrue (l == '0x007b ', 'string.format() should format hex correctly[12]')
assertTrue (n == '7b', 'string.format() should format hex correctly[14]')
a = string.format("%8.2f\n", 1.234)
b = string.format("\n%8.2f", 1.234)
c = string.format("\n%8.2f\n", 1.234)
assertTrue (a == ' 1.23\n', 'string.format() should correctly format patterns that contain new lines.[1]')
assertTrue (b == '\n 1.23', 'string.format() should correctly format patterns that contain new lines.[2]')
assertTrue (c == '\n 1.23\n', 'string.format() should correctly format patterns that contain new lines.[3]')
assertTrue (j == ' 007b', 'string.format() should format hex correctly[10]')
assertTrue (m == '0000007b', 'string.format() should format hex correctly[13]')
end
-- gmatch
local s = "from=world, to=Lua"
local x = string.gmatch(s, "(%w+)=(%w+)")
assertTrue (type(x) == 'function', 'string.gmatch() should return an iterator function')
local a, b, c = x()
assertTrue (a == 'from', 'string.gmatch() iterator should return the first group matched in the string [1]')
assertTrue (b == 'world', 'string.gmatch() iterator should return the second group matched in the string [1]')
assertTrue (c == nil, 'string.gmatch() iterator should return nil after all groups are matched [1]')
local a, b, c = x()
assertTrue (a == 'to', 'string.gmatch() iterator should return the first group matched in the string [2]')
assertTrue (b == 'Lua', 'string.gmatch() iterator should return the second group matched in the string [2]')
assertTrue (c == nil, 'string.gmatch() iterator should return nil after all groups are matched [2]')
local a = x()
assertTrue (a == nil, 'string.gmatch() iterator should return nil after all matches have ben returned')
local x = string.gmatch(s, "%w+=%w+")
local a, b = x()
assertTrue (a == 'from=world', 'string.gmatch() iterator should return the first match when no groups are specified')
assertTrue (b == nil, 'string.gmatch() iterator should return nil as second return value when no groups are specified [1]')
local a, b = x()
assertTrue (a == 'to=Lua', 'string.gmatch() iterator should return the second match when no groups are specified')
assertTrue (b == nil, 'string.gmatch() iterator should return nil as second return value when no groups are specified [2]')
do
local x = string.gmatch(';a;', 'a*')
local a, b, c, d, e, f = x(), x(), x(), x(), x(), x();
assertEqual (a, '', 'string.gmatch() iterator should return correct values [1]')
assertEqual (b, 'a', 'string.gmatch() iterator should return correct values [2]')
assertEqual (c, '', 'string.gmatch() iterator should return correct values [3]')
assertEqual (d, '', 'string.gmatch() iterator should return correct values [4]')
assertEqual (e, nil, 'string.gmatch() iterator should return correct values [5]')
assertEqual (e, nil, 'string.gmatch() iterator should return correct values [6]')
end
-- gsub
a = '<%?xml version="1.0" encoding="UTF%-8"%?>'
b = '<?xml version="1.0" encoding="UTF-8"?><my-xml></my-xml>'
c = string.gsub (b, a, 'moo')
assertTrue (c == 'moo<my-xml></my-xml>', 'string.gsub() should replace the matched part of the string[1]')
-- Not even scraping the surface
a = '%%1'
b = 'Hello %1'
c = string.gsub (b, a, 'world')
assertTrue (c == 'Hello world', 'string.gsub() should replace the matched part of the string[2]')
a = '%d'
b = 'ab5kfd8scf4lll'
c = function (x) return '('..x..')' end
d = string.gsub (b, a, c, 2)
assertTrue (d == 'ab(5)kfd(8)scf4lll', 'string.gsub() should replace the matched part of the string with the value returned from the given map function')
a = "[^:]+"
b = ":aa:bbb:cccc:ddddd:eee:"
c = function (subStr) end
d = string.gsub (b, a, c)
assertTrue (d == ':aa:bbb:cccc:ddddd:eee:', 'string.gsub() should not replace the matched part of the string if the value returned from the map function is nil')
c = function (subStr) return 'X' end
d = string.gsub (b, a, c)
assertTrue (d == ':X:X:X:X:X:', 'string.gsub() should replace the matched part of the string if the value returned from the map function is not nil')
c = string.gsub (';a;', 'a*', 'ITEM')
assertTrue (c == 'ITEM;ITEMITEM;ITEM', 'string.gsub() should replace the matched part of the string[2]')
a = 'abc\\def'
b = string.gsub(a, '\\', '\\\\')
assertEqual (b, 'abc\\\\def', 'string.gsub() should allow backslashes')
a = "a = 'a', b = 'b', c = 'c',"
b = string.gsub(a, ",$", "")
assertEqual (b, "a = 'a', b = 'b', c = 'c'", 'string.gsub() should match $ with end of string')
-- len
local a = 'McLaren Mercedes'
local b = string.len ('');
local c = string.len (a);
assertTrue (b == 0, 'string.len() should return 0 if passed an empty string')
assertTrue (c == 16, 'string.len() should return the length of the string in the first argument')
-- lower
local a = 'McLaren Mercedes'
local b = string.lower ('');
local c = string.lower (a);
assertTrue (b == '', 'string.lower() should return an empty string if passed an empty string')
assertTrue (c == 'mclaren mercedes', 'string.lower() should return the string in the first argument with all character in lower case')
-- match
local a = string.match('20/11/1988', "^%d+%p%d+%p%d%d%d%d$")
assertEqual (a, '20/11/1988', 'string.match() should handle punctuation.')
local a = ('foo@bar.com'):match("^[%w+%.%-_]+@[%w+%.%-_]+%.%a%a+$")
local a = ('test-123_test.2@a-b_c.movie'):match("^[%w+%.%-_]+@[%w+%.%-_]+%.%a%a+$")
assertEqual (a, 'test-123_test.2@a-b_c.movie', 'string.match() should flatten nested groups.')
local a = ('-=[]\';'):match("%W")
assertEqual (a, '-', 'string.match() match non-word chars.')
-- rep
local a = 'Ho'
local b = string.rep (a, 0);
local c = string.rep (a, 1);
local d = string.rep (a, 3);
assertTrue (b == '', 'string.rep() should return an empty string if the second argument is 0')
assertTrue (c == 'Ho', 'string.rep() should return the first argument if the second argument is 1')
assertTrue (d == 'HoHoHo', 'string.rep() should return a string containing the first argument repeated the second argument number of times')
-- reverse
local a = string.reverse ('');
local b = string.reverse ('x');
local c = string.reverse ('tpircSavaJ');
assertTrue (a == '', 'string.reverse() should return an empty string if passed an empty string')
assertTrue (b == 'x', 'string.reverse() should return the first argument if its length is 1')
assertTrue (c == 'JavaScript', 'string.reverse() should return a string containing the first argument reversed')
-- sub
local a = 'Pub Standards'
local b = string.sub (a, 1)
local c = string.sub (a, 5)
local d = string.sub (a, -4)
local e = string.sub (a, 1, 3)
local f = string.sub (a, 7, 9)
local g = string.sub (a, -4, -2)
local h = string.sub (a, 5, -2)
local i = string.sub (a, 0)
assertTrue (b == 'Pub Standards', 'string.sub() should return the first argument if the second argument is 1')
assertTrue (c == 'Standards', 'string.sub() should return a subset of the first argument from the nth character onwards, when n is the second argument and positive')
assertTrue (d == 'ards', 'string.sub() should return the last n characters of the first argument, where n is the absolute value of the second argument and the second argument is negative')
assertTrue (e == 'Pub', 'string.sub() should return the first n characters of the first argument when the second argument is one and n is the third argument')
assertTrue (f == 'and', 'string.sub() should return a subset of the first argument from the nth character to the mth character, when n is the second argument and positive and m is the third argument and negative')
assertTrue (h == 'Standard', 'string.sub() should return a subset of the first argument from the nth character to the last but mth character, when n is the second argument and positive and m is the third argument and negative')
assertTrue (i == 'Pub Standards', 'string.sub() should return a subset of the first argument from the last but nth character to the last but mth character, when n is the second argument and negative and m is the third argument and negative')
-- upper
local a = string.upper ('');
local b = string.upper ('JavaScript');
assertTrue (a == '', 'string.upper() should return an empty string if passed an empty string')
assertTrue (b == 'JAVASCRIPT', 'string.upper() should return the first argument in uppercase')
-- `string` lib as metatable of strings.
local strMeta = getmetatable('')
assertEqual (strMeta.__index, string, 'String lib should be metamethod of string instances.')
a = ('Hey'):lower()
assertEqual (a, 'hey', 'String lib should be metamethod of string instances.')
local foo = "bar"
getmetatable(foo).__index = function () return "Random String" end
assertEqual (foo.random_index, "Random String", 'Metamethod of string instances should be updateable')

@ -0,0 +1,302 @@
-- concat
local a = {2, 4, "moo", 102}
local b = table.concat ({})
local c = table.concat ({}, ':')
local d = table.concat ({}, ', ', 3)
local e = table.concat ({}, ', ', 3, 4)
local f = table.concat (a)
local g = table.concat (a, '-')
local h = table.concat (a, '..', 2)
local i = table.concat (a, '+', 2, 3)
assertTrue (b == '', 'table.concat() should return an empty string if passed an empty table [1]')
assertTrue (c == '', 'table.concat() should return an empty string if passed an empty table [2]')
assertTrue (d == '', 'table.concat() should return an empty string if passed an empty table [3]')
assertTrue (e == '', 'table.concat() should return an empty string if passed an empty table [4]')
assertTrue (f == '24moo102', 'table.concat() should return all items in the table in argument 1 in a string with no spaces, when arguments 2 and 3 are absent')
assertTrue (g == '2-4-moo-102', 'table.concat() should return return all items in the table in argument 1 in a string delimited by argument 2, when argument 3 is absent')
assertTrue (h == '4..moo..102', 'table.concat() should return the items in the table in argument 1 from the nth index in a string delimited by argument 2, when n is the third argument')
assertTrue (i == '4+moo', 'table.concat() should return the items in the table in argument 1 from the nth index to the mth index in a string delimited by argument 2, when n is the third argument and m is the forth argument')
-- getn
do
local a = {'a', 'b', 'c'}
local b = {'a', 'b', 'c', nil}
local c = {'a', nil, 'b', 'c'}
local d = {'a', nil, 'b', 'c', nil}
local e = {'a', 'b', 'c', moo = 123 }
local f = { moo = 123 }
local g = {}
assertTrue (table.getn (a) == 3, 'table.getn() should return the size of the array part of a table')
assertTrue (table.getn (b) == 3, 'table.getn() should ignore nils at the end of the array part of a table')
assertTrue (table.getn (c) == 4, 'table.getn() should include nils in the middle of the array part of a table')
assertTrue (table.getn (d) == 1, 'table.getn() should return the same random value as C implementation when the last item is nil')
assertTrue (table.getn (e) == 3, 'table.getn() should ignore the hash part of a table')
assertTrue (table.getn (f) == 0, 'table.getn() should return zero when the array part of a table is empty')
assertTrue (table.getn (g) == 0, 'table.getn() should return zero when the table is empty')
end
-- insert
local b = {}
local w = table.insert (b, 'Lewis')
local c = {}
local x = table.insert (c, 3, 'Jenson')
local d = {'We', 'exist', 'to'}
local y = table.insert (d, 'win')
local e = {1, 1998, 1, 1999}
local z = table.insert (e, 3, 'Mika')
local f = {'Kimi'}
local z2 = table.insert (f, 4, 2)
assertTrue (b[1] == 'Lewis', 'table.insert() should add argument 2 to the end of the table in argument 1, when the third argument is absent [1]')
assertTrue (b[2] == nil, 'table.insert() should only add argument 2 to the end of the table in argument 1, when the third argument is absent [2]')
assertTrue (c[1] == nil, 'table.insert() should pad the table with nils when the desired index is greater than the length of the table [1]')
assertTrue (c[2] == nil, 'table.insert() should pad the table with nils when the desired index is greater than the length of the table [2]')
assertTrue (c[3] == 'Jenson', 'table.insert() should add argument 2 to the end of the table in argument 1, when the third argument is greater than the length of the table [1]')
assertTrue (c[4] == nil, 'table.insert() should only add argument 2 to the end of the table in argument 1, when the third argument is greater than the length of the table [2]')
assertTrue (d[1] == 'We', 'table.insert() should not affect existing items in the table when the third argument is missing [1]')
assertTrue (d[2] == 'exist', 'table.insert() should not affect existing items in the table when the third argument is missing [2]')
assertTrue (d[3] == 'to', 'table.insert() should not affect existing items in the table when the third argument is missing [3]')
assertTrue (d[4] == 'win', 'table.insert() should add argument 2 to the end of the table in argument 1, when the third argument is missing [1]')
assertTrue (d[5] == nil, 'table.insert() should only add argument 2 to the end of the table in argument 1, when the third argument is missing [2]')
assertTrue (e[1] == 1, 'table.insert() should not affect existing items in the table at indices less than that specified in the third argument [1]')
assertTrue (e[2] == 1998, 'table.insert() should not affect existing items in the table at indices less than that specified in the third argument [2]')
assertTrue (e[3] == 'Mika', 'table.insert() should add argument 3 into the table in argument 1 at the index specified in argument 2')
assertTrue (e[4] == 1, 'table.insert() should shift items in the table in argument 1 down by one after and including the index at argument 2 [1]')
assertTrue (e[5] == 1999, 'table.insert() should shift items in the table in argument 1 down by one after and including the index at argument 2 [2]')
assertTrue (e[6] == nil, 'table.insert() should only add one index to the table in argument 1 [1]')
assertTrue (f[1] == 'Kimi', 'table.insert() should not affect existing items in the table at indices less than that specified in the third argument [3]')
assertTrue (f[2] == nil, 'table.insert() should pad the table with nils when the desired index is greater than the length of the table [3]')
assertTrue (f[3] == nil, 'table.insert() should pad the table with nils when the desired index is greater than the length of the table [4]')
assertTrue (f[4] == 2, 'table.insert() should not affect existing items in the table at indices less than that specified in the third argument [2]')
assertTrue (f[5] == nil, 'table.insert() should only add one index to the table in argument 1 [2]')
assertTrue (w == nil, 'table.insert() should update list in place and return nil')
assertTrue (x == nil, 'table.insert() should update list in place and return nil')
assertTrue (y == nil, 'table.insert() should update list in place and return nil')
assertTrue (z == nil, 'table.insert() should update list in place and return nil')
assertTrue (z2 == nil, 'table.insert() should update list in place and return nil')
local function insertStringKey ()
table.insert({}, 'string key', 1)
end
a, b = pcall(insertStringKey)
assertTrue (a == false, 'table.insert() should error when passed a string key')
local function insertStringKey ()
table.insert({}, '23', 1)
end
a, b = pcall(insertStringKey)
assertTrue (a, 'table.insert() should not error when passed a string key that can be coerced to a number [1]')
local function insertStringKey ()
table.insert({}, '1.23e33', 1)
end
a, b = pcall(insertStringKey)
assertTrue (a, 'table.insert() should not error when passed a string key that can be coerced to a number [2]')
local function insertStringKey ()
table.insert({}, '-23', 1)
end
a, b = pcall(insertStringKey)
assertTrue (a, 'table.insert() should not error when passed a string key that can be coerced to a negative number')
-- maxn
local a = table.maxn ({})
local b = table.maxn ({1, 2, 4, 8})
local c = table.maxn ({nil, nil, 123})
local d = {}
table.insert (d, 3, 'Moo')
local e = table.maxn (d)
assertTrue (a == 0, 'table.maxn() should return zero when passed an empty table')
assertTrue (b == 4, 'table.maxn() should return the highest index in the passed table [1]')
assertTrue (c == 3, 'table.maxn() should return the highest index in the passed table [2]')
assertTrue (e == 3, 'table.maxn() should return the highest index in the passed table [3]')
assertTrue (#d == 0, 'Length operator should return the first empty index minus one [1]')
-- remove
local a = {14, 2, "Hello", 298}
local b = table.remove (a)
local c = {14, 2, "Hello", 298}
local d = table.remove (c, 3)
local e = {14, 2}
local f = table.remove (e, 6)
local g = table.remove ({}, 1)
assertTrue (a[1] == 14, 'table.remove() should not affect items before the removed index [1]')
assertTrue (a[2] == 2, 'table.remove() should not affect items before the removed index [2]')
assertTrue (a[3] == "Hello", 'table.remove() should not affect items before the removed index [3]')
assertTrue (a[4] == nil, 'table.remove() should remove the last item in the table when second argument is absent')
assertTrue (b == 298, 'table.remove() should return the removed item [1]')
assertTrue (c[1] == 14, 'table.remove() should not affect items before the removed index [3]')
assertTrue (c[2] == 2, 'table.remove() should not affect items before the removed index [4]')
assertTrue (c[3] == 298, 'table.remove() should remove the item at the index specified by the second argument and shift subsequent item down')
assertTrue (c[4] == nil, 'table.remove() should decrease the length of the table by one')
assertTrue (d == 'Hello', 'table.remove() should return the removed item [2]')
assertTrue (e[1] == 14, 'table.remove() should not affect items before the removed index [5]')
assertTrue (e[2] == 2, 'table.remove() should not affect items before the removed index [6]')
assertTrue (e[3] == nil, 'table.remove() should not affect the table if the given index is past the length of the table')
assertTrue (f == nil, 'table.remove() should return nil if the given index is past the length of the table [1]')
assertTrue (g == nil, 'table.remove() should return nil if the given index is past the length of the table [2]')
c = {nil, nil, 123}
assertTrue (#c == 3, 'Length operator should return the first empty index minus one [2]')
table.remove (c, 1)
assertTrue (#c == 0, 'Length operator should return the first empty index minus one [3]')
assertTrue (c[1] == nil, 'table.remove() should shift values down if index <= initial length [1]')
assertTrue (c[2] == 123, 'table.remove() should shift values down if index <= initial length [2]')
assertTrue (c[3] == nil, 'table.remove() should shift values down if index <= initial length [3]')
table.remove (c, 1)
assertTrue (#c == 0, 'Length operator should return the first empty index minus one [4]')
assertTrue (c[1] == nil, 'table.remove() should not affect the array if index > initial length [1]')
assertTrue (c[2] == 123, 'table.remove() should not affect the array if index > initial length [2]')
assertTrue (c[3] == nil, 'table.remove() should not affect the array if index > initial length [3]')
table.remove (c, 2)
assertTrue (#c == 0, 'Length operator should return the first empty index minus one [5]')
assertTrue (c[1] == nil, 'table.remove() should not affect the array if index > initial length [4]')
assertTrue (c[2] == 123, 'table.remove() should not affect the array if index > initial length [5]')
assertTrue (c[3] == nil, 'table.remove() should not affect the array if index > initial length [6]')
-- sort
local a = { 1, 2, 3, 6, 5, 4, 20 }
table.sort (a)
assertTrue (a[1] == 1, 'table.sort() should sort elements into alphnumeric order, when not passed a sort function [1]')
assertTrue (a[2] == 2, 'table.sort() should sort elements into alphnumeric order, when not passed a sort function [2]')
assertTrue (a[3] == 3, 'table.sort() should sort elements into alphnumeric order, when not passed a sort function [3]')
assertTrue (a[4] == 4, 'table.sort() should sort elements into alphnumeric order, when not passed a sort function [4]')
assertTrue (a[5] == 5, 'table.sort() should sort elements into alphnumeric order, when not passed a sort function [5]')
assertTrue (a[6] == 6, 'table.sort() should sort elements into alphnumeric order, when not passed a sort function [6]')
assertTrue (a[7] == 20, 'table.sort() should sort elements into alphnumeric order, when not passed a sort function [7]')
assertTrue (a[8] == nil, 'table.sort() should not affect the table if the given index is past the length of the table')
local a = { 1, 2, 3, 6, 5, 4, 20 }
table.sort (a, function (a, b) return b < a end)
assertTrue (a[1] == 20, 'table.sort() should sort elements into order defined by sort function [1]')
assertTrue (a[2] == 6, 'table.sort() should sort elements into order defined by sort function [2]')
assertTrue (a[3] == 5, 'table.sort() should sort elements into order defined by sort function [3]')
assertTrue (a[4] == 4, 'table.sort() should sort elements into order defined by sort function [4]')
assertTrue (a[5] == 3, 'table.sort() should sort elements into order defined by sort function [5]')
assertTrue (a[6] == 2, 'table.sort() should sort elements into order defined by sort function [6]')
assertTrue (a[7] == 1, 'table.sort() should sort elements into order defined by sort function [7]')
assertTrue (a[8] == nil, 'table.sort() should not affect the table if the given index is past the length of the table')
-- unpack
local a = {0, 1, 2, 4, 20, 50, 122}
local b, c, d, e, f, g = table.unpack (a, 3);
local h, i = table.unpack (a, 3, 2);
local j, k, l, m = table.unpack (a, 3, 5);
assertTrue (b == 2, 'table.unpack() should return the correct items of the given list [1]')
assertTrue (c == 4, 'table.unpack() should return the correct items of the given list [2]')
assertTrue (d == 20, 'table.unpack() should return the correct items of the given list [3]')
assertTrue (e == 50, 'table.unpack() should return the correct items of the given list [4]')
assertTrue (f == 122, 'table.unpack() should return the correct items of the given list [5]')
assertTrue (g == nil, 'table.unpack() should return the correct items of the given list [6]')
assertTrue (h == nil, 'table.unpack() should return the correct items of the given list [7]')
assertTrue (i == nil, 'table.unpack() should return the correct items of the given list [8]')
assertTrue (j == 2, 'table.unpack() should return the correct items of the given list [9]')
assertTrue (k == 4, 'table.unpack() should return the correct items of the given list [10]')
assertTrue (l == 20, 'table.unpack() should return the correct items of the given list [11]')
assertTrue (m == nil, 'table.unpack() should return the correct items of the given list [12]')
local a = {nil, nil, 180}
local b, c, d, e = table.unpack (a);
assertTrue (b == nil, 'table.unpack() should return the correct items of the given list [13]')
assertTrue (c == nil, 'table.unpack() should return the correct items of the given list [14]')
assertTrue (d == 180, 'table.unpack() should return the correct items of the given list [15]')
assertTrue (e == nil, 'table.unpack() should return the correct items of the given list [16]')
--Make sure binary searching is implemented the same way as C…
local table1 = {true, nil, true, false, nil, true, nil}
local table2 = {true, false, nil, false, nil, true, nil}
local table3 = {true, false, false, false, true, true, nil}
local a1, b1, c1, d1, e1, f1 = table.unpack (table1);
local a2, b2, c2, d2, e2, f2 = table.unpack (table2);
local a3, b3, c3, d3, e3, f3, g3 = table.unpack (table3);
assertTrue (a1, 'table.unpack() should return the same items as the C implementation [1]')
assertTrue (b1 == nil, 'table.unpack() should return the same items as the C implementation [2]')
assertTrue (c1, 'table.unpack() should return the same items as the C implementation [3]')
assertTrue (not d1, 'table.unpack() should return the same items as the C implementation [4]')
assertTrue (e1 == nil, 'table.unpack() should return the same items as the C implementation [5]')
assertTrue (f1 == nil, 'table.unpack() should return the same items as the C implementation [6]')
assertTrue (a2, 'table.unpack() should return the same items as the C implementation [7]')
assertTrue (not b2, 'table.unpack() should return the same items as the C implementation [8]')
assertTrue (c2 == nil, 'table.unpack() should return the same items as the C implementation [9]')
assertTrue (d2 == nil, 'table.unpack() should return the same items as the C implementation [10]')
assertTrue (e2 == nil, 'table.unpack() should return the same items as the C implementation [11]')
assertTrue (f2 == nil, 'table.unpack() should return the same items as the C implementation [12]')
assertTrue (a3, 'table.unpack() should return the same items as the C implementation [13]')
assertTrue (not b3, 'table.unpack() should return the same items as the C implementation [14]')
assertTrue (not c3, 'table.unpack() should return the same items as the C implementation [15]')
assertTrue (not d3, 'table.unpack() should return the same items as the C implementation [16]')
assertTrue (e3, 'table.unpack() should return the same items as the C implementation [17]')
assertTrue (f3, 'table.unpack() should return the same items as the C implementation [18]')
assertTrue (g3 == nil, 'table.unpack() should return the same items as the C implementation [19]')

@ -0,0 +1,561 @@
-- assert
local ass = function (test)
return assert (test, 'error message')
end
a, b, c = pcall (ass, true)
assertTrue (a, 'Assert should not throw an error when passed true')
assertTrue (b, 'Assert should return the value passed in the first return value')
assertTrue (c == 'error message', 'Assert should return the message passed in the second return value')
a, b, c = pcall (ass, 0)
assertTrue (a, 'Assert should not throw an error when passed 0')
a, b, c = pcall (ass, 1)
assertTrue (a, 'Assert should not throw an error when passed 1')
a, b, c = pcall (ass, '')
assertTrue (a, 'Assert should not throw an error when passed an empty string')
a, b, c = pcall (ass, nil)
assertTrue (not a, 'Assert should throw an error when passed nil')
--assertTrue (b == 'error message', 'Assert should throw an error with the given message')
a, b, c = pcall (ass, false)
assertTrue (not a, 'Assert should throw an error when passed false')
-- getmetatable
local mt = {}
local t = {}
setmetatable(t, mt)
a = getmetatable(t)
b = getmetatable('moo')
c = getmetatable(123)
d = getmetatable({})
e = getmetatable(true)
f = getmetatable(function () end)
g = getmetatable('baa')
assertTrue (a == mt, 'getmetatable() should return a table\'s metatable if set')
assertTrue (type(b) == 'table', 'getmetatable() should return a metatable when passed a string')
assertTrue (b.__index == string, 'getmetatable() should return the string module as a prototype of string')
assertTrue (c == nil, 'getmetatable() should return nil when passed a number')
assertTrue (d == nil, 'getmetatable() should return nil when passed a table without a metatable')
assertTrue (e == nil, 'getmetatable() should return nil when passed a boolean')
assertTrue (f == nil, 'getmetatable() should return nil when passed a function')
assertTrue (g == b, 'The metatable of all strings should be the same table')
-- ipairs
local a = {2,4,8}
local b = ''
for i, v in ipairs(a) do
b = b..'['..i..'='..v..']'
end
assertTrue (b == '[1=2][2=4][3=8]', 'ipairs() should iterate over table items [1]')
local t = {nil, 1, 2}
local s = ''
for i, v in ipairs(t) do
s = s..tostring(i)..'='..tostring(v)..';'
end
assertTrue (s == '', 'ipairs() should not iterate over nil values in a table.')
t = {3, 4, nil, 1, 2}
s = ''
for i, v in ipairs(t) do
s = s..tostring(i)..'='..tostring(v)..';'
end
assertTrue (s == '1=3;2=4;', 'ipairs() should iterate over values up to but not including nil values in a table.')
t = {
[0] = "zero",
[1] = "one",
[2] = "two",
[-1] = "negative",
foo = "string",
[0.5] = "half"
}
local r = {}
for i, v in ipairs(t) do
r[v] = true
end
assertTrue (not r.zero, 'ipairs() should not iterate over zero key')
assertTrue (r.one, 'ipairs() should iterate over positive integer keys [1]')
assertTrue (r.two, 'ipairs() should iterate over positive integer keys [2]')
assertTrue (not r.negative, 'ipairs() should not iterate over negative keys')
assertTrue (not r.string, 'ipairs() should not iterate over string keys')
assertTrue (not r.half, 'ipairs() should not iterate over non-integer numeric keys')
-- load
src = 'return "hello"'
local index = 0
local function getChar ()
index = index + 1
return string.sub(src, index, index)
end
local f = load(getChar)
assertTrue (type(f) == 'function', 'load() should return a function when passed a valid source string')
local result = f();
assertTrue (result == 'hello', 'The function returned from load() should return the value from the script')
-- loadfile
local f = loadfile('scripts/not-a-file.luac')
assertTrue (f == nil, 'loadfile() should return nil when passed an invalid filename')
mainGlobal1 = 'mainGlbl'
mainGlobal2 = 'mainGlbl'
local mainLocal = 'mainLoc'
f = loadfile('lib-loadfile.lua')
assertTrue (type(f) == 'function', 'loadfile() should return a function when passed a valid filename')
local result = f();
assertTrue (type(result) == 'table', 'The function returned from loadfile() should return the value from the script')
assertTrue (type(result.getValue) == 'function', 'The function returned from loadfile() should return the value that is returned from the script[1]')
assertTrue (result.getValue() == 'moo', 'The function returned from loadfile() should return the value that is returned from the script[2]')
assertTrue (mainGlobal1 == 'innerGlbl', 'The function returned from loadfile() should share the same global namespace as the outer script[1]')
assertTrue (mainGlobal2 == 'mainGlbl', 'The function returned from loadfile() should share the same global namespace as the outer script[2]')
assertTrue (innerLocal == nil, 'Function locals should not leak into outer environment in a loadfile() function call')
-- pairs
local a, b = "", {foo=1}
b["bar"] = "Hello",
table.insert(b, 123)
for i, v in pairs(b) do
a = a..i..':'..v..';'
end
assertTrue (#a == #'1:123;bar:Hello;foo:1;', 'pairs() should iterate over table items [2]') -- Have to compare lengths because order is arbitrary
local t = {
[0] = "zero",
[1] = "one",
[-1] = "negative",
foo = "string",
[0.5] = "half"
}
local r = {}
for i, v in pairs(t) do
r[v] = true
end
assertTrue (r.zero, 'pairs() should iterate over zero key')
assertTrue (r.one, 'pairs() should iterate over positive integer keys')
assertTrue (r.negative, 'pairs() should iterate over negative keys')
assertTrue (r.string, 'pairs() should iterate over string keys')
assertTrue (r.half, 'pairs() should iterate over non-integer numberic keys')
t = { nil, nil, 123 }
a = ''
for i, v in pairs(t) do
a = a..i..':'..v..';'
end
assertTrue (a == '3:123;', 'pairs() should iterate over numeric table items')
t = {}
t[10] = {}
t[15] = {}
s = ''
for i in pairs(t) do
s = s..i..';'
end
assertTrue (s == '10;15;', 'pairs() should return correct numeric keys')
-- pcall
function goodfunc (x)
return x + 1, x + 2
end
function badfunc ()
error ('I\'m bad.')
end
a, b, c = pcall (goodfunc, 6)
assertTrue (a == true, 'pcall() should return true in the first item when a function executes successfully')
assertTrue (b == 7, 'pcall() should return the result of the function in the items following the first item returned, when a function executes successfully [1]')
assertTrue (c == 8, 'pcall() should return the result of the function in the items following the first item returned, when a function executes successfully [2]')
a, b, c = pcall (badfunc, 6)
assertTrue (a == false, 'pcall() should return false in the first item when the function errors during execution')
assertTrue (not (b == nil), 'pcall() should return an error message in the second item when the function error during execution')
assertTrue (c == nil, 'pcall() should only return 2 items when the function error during execution')
-- rawequal
-- rawget
-- rawset
-- TODO
-- require
mainGlobal1 = 'mainGlbl'
mainGlobal2 = 'mainGlbl'
moduleInitCount = 0
local mainLocal = 'mainLoc'
local result = require 'lib-require'
assertTrue (type(result) == 'table', 'require() should return a table')
assertTrue (type(result.getValue) == 'function', 'require() should return the value that is returned from the module[1]')
assertTrue (result.getValue() == 'modVal', 'require() should return the value that is returned from the module[2]')
assertTrue (package.loaded['lib-require'] == result, 'Module loaded by require() should also be available in package.loaded[modname]')
assertTrue (moduleInitCount == 1, 'require() should initialise module')
assertTrue (mainGlobal1 == 'innerGlbl', 'require() should pass the same global namespace into the module[1]')
assertTrue (mainGlobal2 == 'mainGlbl', 'require() should pass the same global namespace into the module[2]')
assertTrue (innerLocal == nil, 'Module locals should not leak into outer environment in a require() call')
local result2 = require 'lib-require'
assertTrue (moduleInitCount == 1, 'require() should only initialise a module once')
assertTrue (result == result2, 'require() should return the same value across multiple calls')
-- select
local a, b, c, d = select (3, 2, 4, 6, 8, 10)
assertTrue (a == 6, 'select() should return its own arguments from the (n + 1)th index, where n is the value of the first argument [1]')
assertTrue (b == 8, 'select() should return its own arguments from the (n + 1)th index, where n is the value of the first argument [2]')
assertTrue (c == 10, 'select() should return its own arguments from the (n + 1)th index, where n is the value of the first argument [3]')
assertTrue (d == nil, 'select() should return its own arguments from the (n + 1)th index, where n is the value of the first argument [4]')
local a, b, c, d = select (-2, 2, 4, 6, 8, 10)
assertTrue (a == 8, 'select() should return its own arguments from the (n + 1)th index, where n is the value of the first argument [5]')
assertTrue (b == 10, 'select() should return its own arguments from the (n + 1)th index, where n is the value of the first argument [6]')
assertTrue (c == nil, 'select() should return its own arguments from the (n + 1)th index, where n is the value of the first argument [7]')
assertTrue (d == nil, 'select() should return its own arguments from the (n + 1)th index, where n is the value of the first argument [8]')
local a, b = select ('#', 2, 4, 6, 8, 10)
assertTrue (a == 5, 'select() should return the total number of arguments - 1, when the first argument is "#" [1]')
assertTrue (b == nil, 'select() should return the total number of arguments - 1, when the first argument is "#" [2]')
local f = function ()
local x, y = select ('moo', 2, 4, 6, 8, 10)
end
local a, b = pcall (f)
assertTrue (a == false, 'select() should error if the first argument is not a number or a string with the value of "#"')
-- setmetatable
-- TODO
-- tonumber
local a = tonumber ('1234')
local b = tonumber ('1234 ')
local c = tonumber (' 1234 ')
local d = tonumber ('1234abc')
local e = tonumber ('1234 12')
local f = tonumber ('1.234')
local g = tonumber ('1.234e+5')
local h = tonumber ('1.234e-5')
assertTrue (a == 1234, 'tonumber() should convert basic numeric strings to decimal and default to base 10')
assertTrue (b == 1234, 'tonumber() should convert numeric strings suffixed with spaces [1]')
assertTrue (c == 1234, 'tonumber() should convert numeric strings prefixed with spaces [1]')
assertTrue (d == nil, 'tonumber() should not convert strings containing letters [1]')
assertTrue (e == nil, 'tonumber() should not convert numeric strings containing spaces in the middle [1]')
assertTrue (f == 1.234, 'tonumber() should convert numeric strings of floating point numbers at base 10 [1]')
assertTrue (g == 123400, 'tonumber() should convert numeric strings of exponential (+ve) numbers at base 10 [1]')
assertTrue (h == 0.00001234, 'tonumber() should convert numeric strings of exponential (-ve) numbers at base 10 [1]')
local a = tonumber ('1234', 10)
local b = tonumber ('1234 ', 10)
local c = tonumber (' 1234 ', 10)
local d = tonumber ('1234abc', 10)
local e = tonumber ('1234 12', 10)
local f = tonumber ('1.234', 10)
local g = tonumber ('1.234e+5', 10)
local h = tonumber ('1.234e-5', 10)
assertTrue (a == 1234, 'tonumber() should convert basic numeric strings to decimal with base 10')
assertTrue (b == 1234, 'tonumber() should convert numeric strings suffixed with spaces [2]')
assertTrue (c == 1234, 'tonumber() should convert numeric strings prefixed with spaces [2]')
assertTrue (d == nil, 'tonumber() should not convert strings containing letters [2]')
assertTrue (e == nil, 'tonumber() should not convert numeric strings containing spaces in the middle [2]')
assertTrue (f == 1.234, 'tonumber() should convert numeric strings of floating point numbers at base 10 [2]')
assertTrue (g == 123400, 'tonumber() should convert numeric strings of exponential (+ve) numbers at base 10 [2]')
assertTrue (h == 0.00001234, 'tonumber() should convert numeric strings of exponential (-ve) numbers at base 10 [2]')
local a = tonumber ('101', 2)
local b = tonumber ('101 ', 2)
local c = tonumber (' 101 ', 2)
local d = tonumber ('101abc', 2)
local e = tonumber ('101 10', 2)
local f = tonumber ('101.10', 2)
local g = tonumber ('1.01e+10', 2)
assertTrue (a == 5, 'tonumber() should convert basic numeric strings to decimal with base 2')
assertTrue (b == 5, 'tonumber() should convert numeric strings suffixed with spaces with base 2')
assertTrue (c == 5, 'tonumber() should convert numeric strings prefixed with spaces with base 2')
assertTrue (d == nil, 'tonumber() should not convert strings containing letters with base 2')
assertTrue (e == nil, 'tonumber() should not convert numeric strings containing spaces in the middle with base 2')
assertTrue (f == nil, 'tonumber() should not convert numeric strings of floating point numbers at base 2')
assertTrue (g == nil, 'tonumber() should not convert numeric strings of exponential numbers at base 2')
local a = tonumber ('123', 16)
local b = tonumber ('1AF', 16)
local c = tonumber ('1AF ', 16)
local d = tonumber (' 1AF ', 16)
local e = tonumber ('123Axyz', 16)
local f = tonumber ('123 45', 16)
local g = tonumber ('123.4', 16)
local h = tonumber ('1.23e+10', 16)
assertTrue (a == 291, 'tonumber() should convert basic numeric strings to decimal with base 16')
assertTrue (b == 431, 'tonumber() should convert hexadecimal strings to decimal with base 16')
assertTrue (c == 431, 'tonumber() should convert hexadecimal strings suffixed with spaces with base 16')
assertTrue (d == 431, 'tonumber() should convert hexadecimal strings prefixed with spaces with base 16')
assertTrue (e == nil, 'tonumber() should not convert strings containing letters out of the range of hexadecimal, with base 16')
assertTrue (f == nil, 'tonumber() should not convert hexadecimal strings containing spaces in the middle with base 16')
assertTrue (g == nil, 'tonumber() should not convert hexadecimal strings of floating point numbers at base 16')
assertTrue (h == nil, 'tonumber() should not convert hexadecimal strings of exponential numbers at base 16')
local a = tonumber ('')
local b = tonumber ('', 2)
local c = tonumber ('', 10)
local d = tonumber ('', 16)
assertTrue (a == nil, 'tonumber() should return nil with passed an empty string')
assertTrue (b == nil, 'tonumber() should return nil with passed an empty string with base 2')
assertTrue (c == nil, 'tonumber() should return nil with passed an empty string with base 10')
assertTrue (d == nil, 'tonumber() should return nil with passed an empty string with base 16')
local a = tonumber (nil)
local b = tonumber (0/0)
local c = tonumber (math.huge)
local d = tonumber (-math.huge)
assertTrue (a == nil, 'tonumber() should return nil when passed nil')
assertTrue (b ~= b, 'tonumber() should return nan when passed nan')
assertTrue (c == math.huge, 'tonumber() should return a number when passed inf')
assertTrue (d == -math.huge, 'tonumber() should return a number when passed -inf')
local a = tonumber (123)
local b = tonumber (-123)
local c = tonumber (0)
local d = tonumber { value = 123 }
local e = tonumber (function () return 123 end)
assertTrue (a == 123, 'tonumber() should return a number when passed a number')
assertTrue (b == -123, 'tonumber() should return a negative number when passed a negative number')
assertTrue (c == 0, 'tonumber() should return a zero when passed a zero')
assertTrue (d == nil, 'tonumber() should return nil when passed a table')
assertTrue (e == nil, 'tonumber() should return nil when passed a function')
local a = tonumber ('0xa.2')
local b = tonumber ('0xa.2', 10)
local c = tonumber ('0xa.2', 16)
local d = tonumber ('0xa', 10)
local e = tonumber ('0xa', 16)
local f = tonumber ('0xa', 12)
assertTrue (a == 10.125, 'tonumber() should coerce string when using base 10 [1]')
assertTrue (b == 10.125, 'tonumber() should coerce string when using base 10 [2]')
assertTrue (c == nil, 'tonumber() should return nil when string is invalid [1]')
assertTrue (d == 10, 'tonumber() should coerce string when using base 10 [3]')
assertTrue (e == 10, 'tonumber() should ignore leading "0x" when converting to base 16.')
assertTrue (f == nil, 'tonumber() should return nil when string is invalid [2]')
local a = tonumber (10, 16)
local b = tonumber (0xa, 16)
local c = tonumber ('0xa', 34)
local d = tonumber ('inf')
local e = tonumber ('inf', 16)
local f = tonumber (math.huge, 16)
assertTrue (a == 16, 'tonumber() should coerce first argument to a string [1]')
assertTrue (b == 16, 'tonumber() should coerce first argument to a string [2]')
assertTrue (c == 1132, 'tonumber() should convert "x" correctly for bases greater than 33')
assertTrue (d == math.huge, 'tonumber() should coerce "inf" to inf with base 10')
assertTrue (e == nil, 'tonumber() should coerce "inf" to nil with bases other than 10')
assertTrue (f == nil, 'tonumber() should return nil when passed inf with bases other than 10')
local a = tonumber (0/0, 16)
assertTrue (a == nil, 'tonumber() should return nil when passed inf for bases other than 10')
-- tostring
-- TODO Check for use of __tostring metamethod
a = tostring (123)
b = tostring ({})
c = tostring ({1, 2, 3})
d = tostring (function () return true end)
e = tostring(math.huge)
f = tostring(-math.huge)
g = tostring(0/0)
h = tostring(true)
assertTrue (a == '123', 'tostring() should convert a number to a string')
assertTrue (string.sub(b, 1, 9) == 'table: 0x', 'tostring() should convert an empty table to a string')
assertTrue (string.sub(c, 1, 9) == 'table: 0x', 'tostring() should convert a table to a string')
assertTrue (string.sub(d, 1, 12) == 'function: 0x', 'tostring() should convert a function to a string')
assertTrue (e == 'inf', 'tostring() should convert infinity to "inf"')
assertTrue (f == '-inf', 'tostring() should convert negative infinity to "-inf"')
assertTrue (g == 'nan', 'tostring() should convert not-a-number to "nan"')
assertTrue (h == 'true', 'tostring() should convert a boolean to a string')
a = tostring ('')
b = tostring ('moo')
c = tostring (0)
d = tostring (false)
assertTrue (a == '', 'tostring() should convert a zero-length string to a string')
assertTrue (b == 'moo', 'tostring() should convert a non-zero-length string to a string')
assertTrue (c == '0', 'tostring() should convert zero to a string')
assertTrue (d == 'false', 'tostring() should convert false to a string')
a = {}
setmetatable(a, { __tostring = function () return 'Les Revenants' end })
b = tostring (a)
assertTrue (b == 'Les Revenants', 'tostring() should use __tostring function, if available on metatable')
-- type
local a = type (nil)
local b = type (123)
local c = type ('abc')
local d = type (true)
local e = type ({})
local f = type (function () return true end)
assertTrue (a == 'nil', 'type() should return "nil" for a variable with value of nil')
assertTrue (b == 'number', 'type() should return "number" for a variable with value of number')
assertTrue (c == 'string', 'type() should return "string" for a variable with value of type string')
assertTrue (d == 'boolean', 'type() should return "boolean" for a variable with value of type boolean')
assertTrue (e == 'table', 'type() should return "table" for a variable with value of type table')
assertTrue (f == 'function', 'type() should return "function" for a variable with value of type function')
-- _VERSION
assertTrue (_VERSION == 'Lua 5.3', '_VERSION should be "Lua 5.3"')
-- xpcall
function goodfunc ()
return 10, "win"
end
function badfunc ()
error ('I\'m bad.')
end
function errfunc ()
return 999, "fail"
end
a, b, c, d = xpcall (goodfunc, errfunc)
assertTrue (a == true, 'xpcall() should return true in the first item when a function executes successfully')
assertTrue (b == 10, 'xpcall() should return the result of the function in the items following the first item returned, when a function executes successfully [1]')
assertTrue (c == 'win', 'xpcall() should return the result of the function in the items following the first item returned, when a function executes successfully [2]')
assertTrue (d == nil, 'xpcall() should return the result of the function in the items following the first item returned, when a function executes successfully [3]')
a, b, c = xpcall (badfunc, errfunc)
assertTrue (a == false, 'xpcall() should return false in the first item when the function errors during execution')
assertTrue (b == 999, 'xpcall() should return the first item of the result of the error function in the second item returned, when the function errors during execution')
assertTrue (c == nil, 'xpcall() should only return the first item of the result of the error function in the items following the first item returned, when the function errors during execution')
-- Libs should be preloaded
assertTrue (package.loaded.table == table, 'table library should exist in package.loaded')
assertTrue (package.loaded.string == string, 'string library should exist in package.loaded')
assertTrue (package.loaded.math == math, 'math library should exist in package.loaded')
assertTrue (package.loaded.os == os, 'os library should exist in package.loaded')
assertTrue (package.loaded.package == package, 'package library should exist in package.loaded')
assertTrue (package.loaded._G == _G, '_G should exist in package.loaded')

@ -0,0 +1,395 @@
-- __index
local o = {}
local index = 'mogwai'
local returnVal = {}
local test
local x = {}
--nil
setmetatable(o, {})
assertTrue (o[index] == nil, 'Getting an index of an empty table with empty metamethod should return nil.')
--function
setmetatable(o, { __index = function (t, i)
assertTrue (t == o, '__index function in metatable should be passed the table as first argument.')
assertTrue (i == index, '__index function in metatable should be passed the index as second argument.')
test = true
return returnVal
end })
local result = o[index]
assertTrue (test, '__index function in metatable should be executed when table has no property by that index.')
assertTrue (result == returnVal, 'Value returned from __index function in metatable should be passed as the value')
--table
setmetatable(x, { __index = o });
test = false
result = x[index]
assertTrue (test, '__index function in metatable should be executed when table has no property by that index, even when nested.')
assertTrue (result == returnVal, 'Value returned from __index function in metatable should be passed as the value when nested')
--don't call if assigned
x[index] = 456
test = false
result = x[index]
assertTrue (not test, '__index function in metatable should not be executed when table has a property by that index.')
assertTrue (result == 456, '__index should be ignored when index is set.')
--test diffferent types of keys
setmetatable(o, { __index = function (t, i)
test = true
return returnVal
end })
test = false
result = o[123]
assertTrue (test, '__index function in metatable should be executed when table has no property by numerical index')
assertTrue (result == returnVal, 'Value returned from __index function in metatable should be passed as the value when index is numerical')
test = false
result = o[function () end]
assertTrue (test, '__index function in metatable should be executed when table has no property with a function key')
assertTrue (result == returnVal, 'Value returned from __index function in metatable should be passed as the value with a function key')
test = false
result = o[{}]
assertTrue (test, '__index function in metatable should be executed when table has no property with a table key')
assertTrue (result == returnVal, 'Value returned from __index function in metatable should be passed as the value with a table key')
-- nil (assigned)
getmetatable(o).__index = nil
assertTrue (o[index] == nil, 'When __index property of metatable is nil, value returned should be nil')
-- __newindex
--nil
o = {}
setmetatable(o, {})
o[index] = 123
assertTrue (o[index] == 123, 'Setting an index of an empty table with empty metamethod should set that value.')
--function
local value = {}
test = false
o = {}
setmetatable(o, { __newindex = function (t, i, v)
assertTrue (t == o, '__newindex function in metatable should be passed the table as first argument.')
assertTrue (i == index, '__newindex function in metatable should be passed the index as second argument.')
assertTrue (v == value, '__newindex function in metatable should be passed the value as third argument.')
test = true
return returnVal
end })
o[index] = value
assertTrue (test, '__newindex function in metatable should be executed when table has no property by that index.')
assertTrue (o[index] == nil, '__newindex function should not set the value unless done so explicitly,')
--table does not have same effect as __index
x = {}
setmetatable(x, { __index = o });
test = false
x[index] = value
assertTrue (not test, '__newindex function in metatable should not be executed when nested.')
assertTrue (x[index] == value, '__newindex function in metatable should be be ignored when nested.')
--don't call if assigned
test = false
rawset(o, index, 111)
o[index] = value
assertTrue (not test, '__newindex function in metatable should not be executed when table has a property by that index.')
assertTrue (o[index] == value, '__newindex should be ignored when index is set.')
--test different types of keys
setmetatable(o, { __newindex = function (t, i, v)
test = true
return returnVal
end })
test = false
index = 123
o[index] = value
assertTrue (test, '__newindex function in metatable should be executed when table has not property for numerical key.')
assertTrue (o[index] == nil, '__newindex should return the correct value when passed a numerical key.')
test = false
index = function () end
o[index] = value
assertTrue (test, '__newindex function in metatable should be executed when table has not property for function key.')
assertTrue (o[index] == nil, '__newindex should return the correct value when passed a function key.')
test = false
index = {}
o[index] = value
assertTrue (test, '__newindex function in metatable should be executed when table has not property for table key.')
assertTrue (o[index] == nil, '__newindex should return the correct value when passed a table key.')
-- nil (assigned)
rawset(o, index, nil)
getmetatable(o).__index = nil
assertTrue (o[index] == nil, 'When __index property of metatable is nil, value returned should be nil')
-- metatable
local mt = { moo = '123' }
local fake = {}
local fake2 = {}
o = {}
setmetatable(o, mt)
result = getmetatable(o)
assertTrue (result == mt, 'getmetatable() should return metatable when __metatable is not set')
mt.__metatable = fake
result = getmetatable(o)
assertTrue (result ~= mt, 'getmetatable() should not return metatable when __metatable is set')
assertTrue (result == fake, 'getmetatable() should return the value of __metatable, if set')
local setmet = function ()
setmetatable(o, mt)
end
local s, _ = pcall(setmet)
assertTrue (not s, 'setmetatable() should error when metatable has __metatable set')
mt.__metatable = function () return fake2 end
result = getmetatable(o)
assertTrue (result ~= fake2, 'getmetatable() should not return the value returned by __metatable, if it is set to a function')
assertTrue (type(result) == 'function', 'getmetatable() should return the value of __metatable, even if it is set to a function')
-- Arithmetic metamethods
local mt = {}
local Obj = {}
function Obj.new (v)
local self = { ['value'] = v }
setmetatable (self, mt);
return self
end
local o = Obj.new (3);
local p = Obj.new (5);
local x = { value = 'moo' }
-- __add
mt.__add = function (a, b)
return a.value..'(__add)'..b.value
end
assertTrue (o + p == '3(__add)5', 'Add operator should use __add metamethod, if provided [1]')
assertTrue (o + x == '3(__add)moo', 'Add operator should use __add metamethod, if provided [2]')
assertTrue (x + p == 'moo(__add)5', 'Add operator should use __add metamethod, if provided [3]')
-- __concat
mt.__concat = function (a, b)
return a.value..'(__concat)'..b.value
end
assertTrue (o..p == '3(__concat)5', 'Concatenation operator should use __concat metamethod, if provided [1]')
assertTrue (o..x == '3(__concat)moo', 'Concatenation operator should use __concat metamethod, if provided [2]')
assertTrue (x..p == 'moo(__concat)5', 'Concatenation operator should use __concat metamethod, if provided [3]')
-- __div
mt.__div = function (a, b)
return a.value..'(__div)'..b.value
end
assertTrue (o / p == '3(__div)5', 'Divide operator should use __div metamethod, if provided [1]')
assertTrue (o / x == '3(__div)moo', 'Divide operator should use __div metamethod, if provided [2]')
assertTrue (x / p == 'moo(__div)5', 'Divide operator should use __div metamethod, if provided [3]')
-- __mod
mt.__mod = function (a, b)
return a.value..'(__mod)'..b.value
end
assertTrue (o % p == '3(__mod)5', 'Modulo operator should use __mod metamethod, if provided [1]')
assertTrue (o % x == '3(__mod)moo', 'Modulo operator should use __mod metamethod, if provided [2]')
assertTrue (x % p == 'moo(__mod)5', 'Modulo operator should use __mod metamethod, if provided [3]')
-- __mul
mt.__mul = function (a, b)
return a.value..'(__mul)'..b.value
end
assertTrue (o * p == '3(__mul)5', 'Muliplication operator should use __mul metamethod, if provided [1]')
assertTrue (o * x == '3(__mul)moo', 'Muliplication operator should use __mul metamethod, if provided [2]')
assertTrue (x * p == 'moo(__mul)5', 'Muliplication operator should use __mul metamethod, if provided [3]')
-- __pow
mt.__pow = function (a, b)
return a.value..'(__pow)'..b.value
end
assertTrue (o ^ p == '3(__pow)5', 'Exponentiation operator should use __pow metamethod, if provided [1]')
assertTrue (o ^ x == '3(__pow)moo', 'Exponentiation operator should use __pow metamethod, if provided [2]')
assertTrue (x ^ p == 'moo(__pow)5', 'Exponentiation operator should use __pow metamethod, if provided [3]')
-- __sub
mt.__sub = function (a, b)
return a.value..'(__sub)'..b.value
end
assertTrue (o - p == '3(__sub)5', 'Subtraction operator should use __sub metamethod, if provided [1]')
assertTrue (o - x == '3(__sub)moo', 'Subtraction operator should use __sub metamethod, if provided [2]')
assertTrue (x - p == 'moo(__sub)5', 'Subtraction operator should use __sub metamethod, if provided [3]')
-- __unm
mt.__unm = function (a)
return '(__unm)'..a.value
end
assertTrue (-o == '(__unm)3', 'Negation operator should use __unm metamethod, if provided')
-- Relational metamethods
-- __eq
local x = 0
mt.__eq = function (a, b)
x = x + 1
return true
end
assertTrue (o == p, 'Equality operator should use __eq metamethod, if provided [1]')
assertTrue (x == 1, 'Equality operator should use __eq metamethod, if provided [2]')
assertTrue (not (o == 123), 'Equality operator should not use __eq metamethod if objects are of different type [1]')
assertTrue (x == 1, 'Equality operator should not use __eq metamethod if operands are of different type [2]')
assertTrue (o == o, 'Equality operator should not use __eq metamethod if the operands are the same object [1]')
assertTrue (x == 1, 'Equality operator should not use __eq metamethod if the operands are the same object [2]')
-- __le
x = 0
mt.__le = function (a, b)
x = x + 1
return a.value == 3
end
assertTrue (o <= p, 'Less than or equal to operator should use __le metamethod, if provided [1]')
assertTrue (x == 1, 'Less than or equal to operator should use __le metamethod, if provided [2]')
assertTrue (not (p <= o), 'Less than or equal to operator should use __le metamethod, if provided [3]')
assertTrue (x == 2, 'Less than or equal to operator should use __le metamethod, if provided [4]')
-- __lt
x = 0
mt.__lt = function (a, b)
x = x + 1
return a.value == 3
end
assertTrue (o < p, 'Less than operator should use __le metamethod, if provided [1]')
assertTrue (x == 1, 'Less than operator should use __le metamethod, if provided [2]')
assertTrue (not (p < o), 'Less than operator should use __le metamethod, if provided [3]')
assertTrue (x == 2, 'Less than operator should use __le metamethod, if provided [4]')
-- __call
x = ''
mt.__concat = nil
mt.__call = function (p1, p2)
if p1 == o then
x = 'Ron '
end
x = x .. p2
return 'CEO'
end
y = o('Dennis')
assertTrue (x == 'Ron Dennis', 'When executing a table, __call metamethod should be used, if provided')
assertTrue (y == 'CEO', 'When executing a table with a __call metamethod, the return value(s) of __call function should be returned')

@ -0,0 +1,278 @@
local a = 1
assertTrue (a == 1, 'Local should retain value')
local a, b = 12, 34
a, b = b, a
assertTrue (a == 34, 'Assignment should be able to reverse values [1]')
assertTrue (b == 12, 'Assignment should be able to reverse values [2]')
local a, b, c, d = 5, 20, 0, nil
assertTrue (a == 5, 'Local should change value')
assertTrue (b == 20, 'Local should accept multiple assignments')
local i = 3
local t = {}
i, t[i] = i+1, 20
assertTrue (t[3] == 20, 'All expressions should evaluate first [0]')
local x = 1
local y = 2
x, y = y, x
assertTrue (x == 2, 'All expressions should evaluate first [1]')
assertTrue (y == 1, 'All expressions should evaluate first [2]')
local x = 1
local y = 2
local z = 3
x, y, z = y, z, x
assertTrue (x == 2, 'All expressions should evaluate first [3]')
assertTrue (y == 3, 'All expressions should evaluate first [4]')
assertTrue (z == 1, 'All expressions should evaluate first [5]')
local result = a + b
assertTrue (result == 25, 'Plus operator should result in addition of operands')
result = a - b
assertTrue (result == -15, 'Minus operator should result in subtraction of operands')
result = a * b
assertTrue (result == 100, 'Asterisk operator should result in multiplication of operands')
result = b / a
assertTrue (result == 4, 'Slash operator should result in division of operands')
result = a / b
assertTrue (result == .25, 'Division should handle floating point results')
result = a / c
assertTrue (result == math.huge, 'Division by zero should return infinity')
result = a / -c
assertTrue (result == -math.huge, 'Division by negative zero should return negative infinity')
xpcall(function () result = a / d end, function () result = 'failed' end)
assertTrue (result == 'failed', 'Division by nil should error')
xpcall(function () result = a / 'x' end, function () result = 'failed2' end)
assertTrue (result == 'failed2', 'Division by string value should error')
xpcall(function () result = 'x' / a end, function () result = 'failed3' end)
assertTrue (result == 'failed3', 'Division of string value should error')
result = 5 % 3
assertTrue (result == 2, 'Modulo operator should return the remainder of the division of the two operands')
result = #'moo\0moo'
assertTrue (result == 7, 'Length operator should return the correct length of string with null character inside')
result = #'moo\0'
assertTrue (result == 4, 'Length operator should return the correct length of string with null character appended')
function testUnpack()
return 1, 2, 3
end
local a, b, c = testUnpack(), 8
assertTrue (a == 1, 'Local assignment should extract the first return value of a function call [1]')
assertTrue (b == 8, 'Local assignment should only extract the first return value when function call not at end of expression list')
assertTrue (c == nil, 'Local assignment should know when to stop')
local a, b, c = 8, testUnpack()
assertTrue (a == 8, 'Local assignment should set the first value from expression list')
assertTrue (b == 1, 'Local assignment should extract the first return value of a function call [2]')
assertTrue (c == 2, 'Local assignment should extract all return values when function call at end of expression list')
local a, b, c
do
a, b, c = testUnpack(), 8
assertTrue (a == 1, 'Assignment should extract the first return value of a function call [1]')
assertTrue (b == 8, 'Assignment should only extract the first return value when function call not at end of expression list')
assertTrue (c == nil, 'Assignment should know when to stop')
a, b, c = 8, testUnpack()
assertTrue (a == 8, 'Assignment should set the first value from expression list')
assertTrue (b == 1, 'Assignment should extract the first return value of a function call [2]')
assertTrue (c == 2, 'Assignment should extract all return values when function call at end of expression list')
end
a = 5
b = 20
do
local a = 5
local b = 3
local c = 5.5
local d = 23
local e = 7
local f = 0
local g = 0 / 0 -- nan
local h = math.huge
local i = -math.huge
assertEqual (a % b, 2, 'Modulo operator should return the remainder of the division of the two operands')
assertEqual (c % b, 2.5, 'Modulo operator should return the fraction part of the remainder of the division of the two operands')
assertEqual (-d % e, 5, 'Modulo operator should always return a positive number if the divisor is positive and wrap around if passed a negative dividend')
assertEqual (d % -e, -5, 'Modulo operator should always return a negative number if the divisor is negative')
assertEqual (-d % -e, -2, 'Modulo operator should always wrap around when passed a negative dividend')
assertEqual (d % f, g, 'Modulo operator should always return "nan" when passed zero as a divisor')
assertEqual (f % d, 0, 'Modulo operator should return zero when passed zero as a dividend (unless divisor == 0)')
assertEqual (f % f, g, 'Modulo operator should return "nan" when passed zero as a dividend and divisor')
assertEqual (d % g, g, 'Modulo operator should return "nan" when passed "nan" as a divisor')
assertEqual (g % d, g, 'Modulo operator should return "nan" when passed "nan" as a dividend')
assertEqual (d % h, g, 'Modulo operator should return "nan" when passed "inf" as a divisor')
assertEqual (h % d, g, 'Modulo operator should return "nan" when passed "inf" as a dividend')
assertEqual (d % i, g, 'Modulo operator should return "nan" when passed "-inf" as a divisor')
assertEqual (i % d, g, 'Modulo operator should return "nan" when passed "-inf" as a dividend')
end
assertTrue (a == a, 'Equality operator should return true if first operand is equal to second')
assertTrue (not (a == b), 'Equality operator should return false if first operand is not equal to second')
assertTrue (a < b, 'Less than should return true if first operand is less than second')
assertTrue (not (a < a), 'Less than should return false if first operand is equal to second')
assertTrue (not (b < a), 'Less than should return false if first operand is greater than second')
assertTrue (b > a, 'Greater than should return true if first operand is Greater than second')
assertTrue (not (a > a), 'Greater than should return false if first operand is equal to second')
assertTrue (not (a > b), 'Greater than should return false if first operand is less than second')
assertTrue (a <= b, 'Less than or equal to should return true if first operand is less than second')
assertTrue (a <= a, 'Less than or equal to should return true if first operand is equal to second')
assertTrue (not (b <= a), 'Less than or equal to should return false if first operand is greater than second')
assertTrue (b >= a, 'Greater than or equal to should return true if first operand is Greater than second')
assertTrue (a >= a, 'Greater than or equal to should return true if first operand is equal to second')
assertTrue (not (a >= b), 'Greater than or equal to should return false if first operand is less than second')
local t = true
local f = false
local n
assertTrue (t, 'True should be true')
assertTrue (0, '0 should coerce to true')
assertTrue (1, '1 should coerce to true')
assertTrue ('moo', 'A string should coerce to true')
assertTrue ('', 'An empty string should coerce to true')
assertTrue ({}, 'An empty table should coerce to true')
assertTrue (not f, 'False should coerce to false')
assertTrue (not n, 'nil should coerce to false')
assertTrue (t and t, 'And operator should return true if both operands are true')
assertTrue (not (f and t), 'And operator should return false if first operand is false')
assertTrue (not (t and f), 'And operator should return false if second operand is false')
assertTrue (not (f and f), 'And operator should return false if both operands are false')
assertTrue (t or t, 'Or operator should return true if both operands are true')
assertTrue (f or t, 'Or operator should return true even if first operand is false')
assertTrue (t or f, 'Or operator should return true even if second operand is false')
assertTrue (not (f or f), 'Or operator should return false if both operands are false')
assertEqual(t and 0 or 1, 0, 'Ternary logic should return the correct result[1]')
assertEqual(f and 0 or 1, 1, 'Ternary logic should return the correct result[2]')
function f(x)
return x
end
assertEqual (f('moo') or false, 'moo', 'Or operator should work with function calls as left operand (+ve)')
assertEqual (f(false) or false, false, 'Or operator should work with function calls as left operand (-ve)')
assertEqual (false or f('moo'), 'moo', 'Or operator should work with function calls as right operand (+ve)')
assertEqual (false or f(false), false, 'Or operator should work with function calls as right operand (-ve)')
assertEqual (f(false) or f('moo'), 'moo', 'Or operator should work with function calls as both operands')
assertEqual (f('moo') and 'baa', 'baa', 'And operator should work with function calls as left operand (+ve)')
assertEqual (f(false) and true, false, 'And operator should work with function calls as left operand (-ve)')
assertEqual (true and f('moo'), 'moo', 'And operator should work with function calls as right operand (+ve)')
assertEqual (true and f(false), false, 'And operator should work with function calls as right operand (-ve)')
assertEqual (f('moo') and f('moo'), 'moo', 'And operator should work with function calls as both operands')
local function test()
return true
end
assertTrue(test() and true, 'Should allow function calls as first operand in boolean operations')
assertTrue(true and test(), 'Should allow function calls as second operand in boolean operations')
local tests = {
addition = function (a, b) return a + b end,
subtraction = function (a, b) return a - b end,
muliplication = function (a, b) return a * b end,
division = function (a, b) return a / b end,
modulus = function (a, b) return a % b end,
pow = function (a, b) return a ^ b end,
['unary-minus'] = function (a, b) return -a, -b end
}
for name, test in pairs(tests) do
local success, result = pcall (test, 5, 2)
assertTrue (success, 'Simple use of '..name..' operator should not fail')
success, result = pcall (test, '3', 6)
assertTrue (success, 'Applying '..name..' operator to a string containing a number should not error [1]')
success, result = pcall (test, '3.', 9)
assertTrue (success, 'Applying '..name..' operator to a string containing a number should not error [2]')
success, result = pcall (test, '3.2', 9)
assertTrue (success, 'Applying '..name..' operator to a string containing a number should not error [3]')
success, result = pcall (test, '3.2e4', 9)
assertTrue (success, 'Applying '..name..' operator to a string containing an exponenial number should not error [4]')
success, result = pcall (test, 8, '2')
assertTrue (success, 'Passing a string containing a number to the '..name..' operator should not error [1]')
success, result = pcall (test, 1, '2.')
assertTrue (success, 'Passing a string containing a number to the '..name..' operator should not error [2]')
success, result = pcall (test, 1, '2.5')
assertTrue (success, 'Passing a string containing a number to the '..name..' operator should not error [3]')
success, result = pcall (test, 1, '2.5e3')
assertTrue (success, 'Passing a string containing an exponential number to the '..name..' operator should not error [4]')
success, result = pcall (test, '9', '2')
assertTrue (success, 'Applying '..name..' operator to two strings containing a numbers should not error')
success, result = pcall (test, 'a', 2)
assertTrue (not success, 'Applying '..name..' operator to an alpha string should error [1]')
success, result = pcall (test, '8a', 2)
assertTrue (not success, 'Applying '..name..' operator to an alpha string should error [2]')
success, result = pcall (test, 'a8', 2)
assertTrue (not success, 'Applying '..name..' operator to an alpha string should error [3]')
success, result = pcall (test, 8, '2a')
assertTrue (not success, 'Passing an alpha string to the '..name..' operator should error')
end
assertTrue('abc' < 'def', 'Strings should be comparable')
local a = '\t'
local b = [[\n\t]]
local c = [[\]]
local d = '\32'
local e = [[\32]]
local f = '\0321'
assertTrue(a == ' ', 'A quoted string literal should escape chars after backslash')
assertTrue(b == '\\n\\t', 'A long bracketed string literal should not escape chars after backslash [1]')
assertTrue(c == '\\', 'A long bracketed string literal should not escape chars after backslash [2]')
assertTrue(d == ' ', 'An escaped number should be converted to a char in quoted string literals')
assertTrue(e == '\\32', 'An escaped number should not be converted to a char in long bracketed string literals')
assertTrue(f == ' 1', 'An escaped number should only consume 3 digits in quoted string literals')

@ -0,0 +1,138 @@
a = {1,2,3,4}
b = a
assertTrue (a == b, 'Tables should be able to be compared by identity')
assertTrue (not (a == {1,2,3,4}), 'Tables should not be able to be compared to literals')
assertTrue (#a == 4, 'Length operator should return the number of items in a table')
assertTrue (a[1] == 1, 'Square brackets operation on table should return correct value for index [1]')
assertTrue (a[2] == 2, 'Square brackets operation on table should return correct value for index [2]')
assertTrue (a[3] == 3, 'Square brackets operation on table should return correct value for index [3]')
assertTrue (a[4] == 4, 'Square brackets operation on table should return correct value for index [4]')
assertTrue (a[5] == nil, 'Square brackets operation on table should return nil for an index greater than the length')
assertTrue (a[0] == nil, 'Square brackets operation on table should return nil for an index of 0')
assertTrue (a[-1] == nil, 'Square brackets operation on table should return nil for an index less than 0')
a = {[1] = 20, [3] = 40}
assertTrue (a[1] == 20, 'Square brackets operation on table should return correct value for index when keys are used in literal assignment [1]')
assertTrue (a[2] == nil, 'Square brackets operation on table should return correct value for index when keys are used in literal assignment [2]')
assertTrue (a[3] == 40, 'Square brackets operation on table should return correct value for index when keys are used in literal assignment [3]')
assertTrue (a[4] == nil, 'Square brackets operation on table should return correct value for index when keys are used in literal assignment [4]')
b = 'hello'
a = {[b] = 'value'}
assertEqual (a.hello, 'value', 'Identifier should be able to be used as a key')
b = 2
a = {[b] = 'value2'}
assertEqual (a[2], 'value2', 'Numeric identifier should be able to be used as a key')
-- TABLES
Account = { balance = 0 }
function Account:new (o)
o = o or {}
setmetatable (o,self)
self.__index = self
return o
end
function Account:deposit (v)
self.balance = self.balance + v
end
function Account:withdraw (v)
if v > self.balance then error "insufficient funds" end
self.balance = self.balance - v
end
acc = Account:new ()
assertTrue (acc.balance == 0, 'Class properties should be initiated when instantiated [1]')
acc:deposit (20)
assertTrue (acc.balance == 20, 'Class instance properties should be updatable though instance method calls [1]')
acc:withdraw (5)
assertTrue (acc.balance == 15, 'Class instance properties should maintain their value in the instance')
acc2 = Account:new ()
assertTrue (acc2.balance == 0, 'Class properties should be initiated when instantiated [2]')
acc2:deposit (50)
assertTrue (acc2.balance == 50, 'Class instance properties should be updatable though instance method calls [2]')
assertTrue (acc.balance == 15, 'Class instance properties should maintain their value separate to other instances')
SpecialAccount = Account:new ()
function SpecialAccount:withdraw (v)
if v - self.balance >= self:getLimit () then
error "insufficient funds"
end
self.balance = self.balance - v
end
function SpecialAccount:getLimit ()
return self.limit or 0
end
s = SpecialAccount:new {limit=1000.00}
assertTrue (s.balance == 0, 'Class properties should be initiated when instantiated, even if class is inherited')
assertTrue (s:getLimit () == 1000, 'Inherited class should have its own properties')
assertTrue (acc.getLimit == nil, 'Base class properties should not change when inherited class manipulated')
s:deposit (500)
assertTrue (s.balance == 500, 'Class instance properties should be updatable though instance method calls [3]')
function f ()
return 1, 3, 9
end
local t = {f()}
assertTrue (t[1] == 1, 'Table should be able to be instantiated by the result of a function [1]')
assertTrue (t[2] == 3, 'Table should be able to be instantiated by the result of a function [2]')
assertTrue (t[3] == 9, 'Table should be able to be instantiated by the result of a function [3]')
t = {}
t[1] = 'number'
t['1'] = 'string'
assertTrue (t[1] == 'number', 'A numerical table index should return a different value than when using the same index as a sting. [1]')
assertTrue (t['1'] == 'string', 'A numerical table index should return a different value than when using the same index as a sting. [2]')
function testUnpack()
return 1, 2, 3
end
t = { testUnpack(), 10 }
assertTrue (t[1] == 1, 'Function call in table literal should pass return value as argument')
assertTrue (t[2] == 10, 'Function call in middle of table literal should only pass one argument')
assertTrue (t[3] == nil, 'Table properties should stop at the end of table literal list')
t = { 10, testUnpack() }
assertTrue (t[1] == 10, 'Table literal should set properties in order')
assertTrue (t[2] == 1, 'Function call in table literal should set properties')
assertTrue (t[3] == 2, 'Function call at end of table literal should set properties for all return values')

@ -0,0 +1,84 @@
local passed, failed = 0, 0
local startTime
local currentFile
if getTimestamp then
startTime = getTimestamp()
end
function assertTrue (condition, message)
if not condition then
failed = failed + 1
reportError(message)
else
passed = passed + 1
end
end
function assertEqual (actual, expected, message)
if actual ~= expected and (actual == actual or expected == expected) then
failed = failed + 1
reportError(message..'; expected "'..tostring(expected)..'", got "'..tostring(actual)..'".')
else
passed = passed + 1
end
end
function run (modName)
currentFile = modName
dofile(modName..'.lua')
end
function reportError (message)
if currentFile ~= lastErrorFile then
print('\n-['..currentFile..']-----------------------------------------')
end
lastErrorFile = currentFile
print('- '..message)
end
function showResults ()
local durationStr = ''
if getTimestamp then
local endTime = getTimestamp()
durationStr = '\nCompleted in '..(endTime - startTime)..'ms.'
end
print "\n------------------------"
if failed == 0 then
print " Passed."
else
print "FAILED!"
end
print "------------------------\n"
print ("Total asserts: "..(passed + failed).."; Passed: "..passed.."; Failed: "..failed..durationStr)
os.exit(failed)
end
run 'operators'
run 'functions'
run 'tables'
run 'control-structures'
run 'coercion'
run 'metamethods'
run 'lib'
run 'lib-string'
run 'lib-table'
run 'lib-math'
-- run 'lib-coroutine'
run 'lib-date'
showResults()

@ -0,0 +1,29 @@
const fs = require('fs')
const path = require('path')
const luainjs = require('..')
let exitCode = 0
{
const rootPath = './tests/starlight/'
const luaEnv = luainjs.createEnv({
fileExists: p => fs.existsSync(path.join(rootPath, p)),
loadFile: p => fs.readFileSync(path.join(rootPath, p), { encoding: 'utf8' }),
osExit: code => (exitCode += code)
})
luaEnv.runfile('test-runner.lua')
}
// TODO: make more official lua 5.3 tests pass (most of them don't pass because they `require "debug"`)
{
const rootPath = './tests/lua-5.3/'
const luaEnv = luainjs.createEnv({
fileExists: p => fs.existsSync(path.join(rootPath, p)),
loadFile: p => fs.readFileSync(path.join(rootPath, p), { encoding: 'utf8' }),
osExit: code => process.exit(code)
})
luaEnv.runfile('goto.lua')
luaEnv.runfile('bwcoercion.lua')
}
process.exit(exitCode)

@ -0,0 +1,32 @@
{
"include": ["src/**/*"],
"exclude": ["node_modules/*"],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"declarationDir": "dist/types",
"target": "es6",
"module": "es2015",
"moduleResolution": "node",
"lib": ["es6", "dom"],
"pretty": true,
"sourceMap": true,
"alwaysStrict": true,
"removeComments": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true
}
}

@ -1,41 +0,0 @@
'use strict'
const path = require('path')
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
mode: 'production',
target: 'node',
entry: './src/index',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'lua-in-js.js',
library: 'lua-in-js',
libraryTarget:'umd'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: [
["@babel/preset-env", {
targets: {
node: "current"
}
}]
]
}
}
]
}
]
},
plugins: [
new CleanWebpackPlugin(['dist'])
]
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save