lyaml: vendor import lyaml bindings for libyaml version 6.2.8

This commit is contained in:
Baptiste Daroussin 2025-06-26 09:09:58 +02:00
commit 95286bbb79
31 changed files with 7317 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
*~
*.o
*.so
*.src.rock
/ChangeLog
/build-aux/config.ld
/luacov.*.out
/lyaml-*.tar.gz
/TAGS

8
.luacov Normal file
View file

@ -0,0 +1,8 @@
modules = {
['lyaml'] = 'lib/lyaml/init.lua',
['lyaml.*'] = 'lib',
}
runreport = true
tick = true

6
AUTHORS Normal file
View file

@ -0,0 +1,6 @@
lyaml is the work of several authors (see git history for
contributors).
There is an earlier C LibYAML binding
Copyright Andrew Danforth <acd@weirdness.net>

27
LICENSE Normal file
View file

@ -0,0 +1,27 @@
This software comprises files that are copyright their respective
authors (see the AUTHORS file for details), and distributed under
the terms of the MIT license (the same license as Lua itself),
unless noted otherwise in the body of that file.
====================================================================
Copyright (C) 2013-2022 Gary V. Vaughan
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 NONINFRINGE-
MENT. 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.
====================================================================

352
NEWS.md Normal file
View file

@ -0,0 +1,352 @@
# lyaml NEWS - User visible changes
## Noteworthy changes in release 6.2.8 (2022-10-22) [stable]
### Bug fixes
- `luke` no longer crashes in `std.normalize` require loops
occasionally in Lua 5.4.
- lyaml emitter no longer leaks at least six bytes for every
map, sequence and scalar emitted.
## Noteworthy changes in release 6.2.7 (2020-11-27) [stable]
### Bug fixes
- Don't skip YAML entries from mixed key Lua tables.
## Noteworthy changes in release 6.2.6 (2020-08-28) [stable]
### Bug fixes
- `luke` really propagates `LDFLAGS` to module compilation
commands.
## Noteworthy changes in release 6.2.5 (2020-04-15) [stable]
### Bug fixes
- `luke` really propagates `YAML_BINDIR`, `YAML_DIR`,
`YAML_INCDIR` and `YAML_LIBDIR` to checksymbol test in lukefile
given the change to `external_dependencies` layout in 6.1.2.
## Noteworthy changes in release 6.2.4 (2019-07-20) [stable]
### Bug fixes
- `luke` works with upgraded bootstrap luarocks version of
`require`.
## Noteworthy changes in release 6.2.3 (2018-09-16) [stable]
### New Features
- Initial support for Lua 5.4.
## Noteworthy changes in release 6.2.2 (2018-03-28) [stable]
### Bug fixes
- Remove spurious dependency on `std.normalize` and `std._debug`
libraries.
## Noteworthy changes in release 6.2.1 (2018-02-20) [stable]
### Bug fixes
- `spec/spec_helper.lua` now looks in the correct objdir
for object modules built by luke, instead of adding unused
paths from old Autotools objdirs. So now specl is properly
running examples against the not yet installed lyaml objects.
## Noteworthy changes in release 6.2 (2017-11-26) [stable]
### Bug fixes
- `luke` uses the correct spelling of LIBFLAG to match luarocks now.
- `luke` no longer throws spurious `cp: file exists` errors.
- `luke` works on luajit again.
## Noteworthy changes in release 6.1.3 (2017-05-29) [stable]
### Bug fixes
- `luke` no longer bombs out with a nil concat error.
## Noteworthy changes in release 6.1.2 (2017-04-30) [stable]
### Bug fixes
- `luke` now propagates `LUA_DIR`, `YAML_INCDIR` and `YAML_LIBDIR`
correctly.
## Noteworthy changes in release 6.1.1 (2017-01-22) [stable]
### New Features
- Builds and installs with `luke` instead of Autotools.
## Noteworthy changes in release 6.1 (2016-10-08) [stable]
### Bug fixes
- `lyaml.load` now correctly reads implicit null scalars in a YAML
document as an `lyaml.null` reference, identical to the "~"
shorthand syntax, according to [the specification][nullspec].
```yaml
empty:
canonical: ~
english: null
~: null key
```
## Noteworthy changes in release 6.0 (2015-07-27) [stable]
### New Features
- `lyaml.load` now correctly reads a !!bool tagged scalar from a
YAML document, or an implicit bool value, according to
[the specification][boolspec].
```yaml
%TAG ! tag:yaml.org,2002:
---
truthy:
- !bool Y
- !bool y
- !bool True
- !bool "on"
falsey:
- !bool n
- !bool OFF
- !bool garbage
```
- `lyaml.load` now correctly reads a !!float tagged scalar from a
YAML document, or an implicit float value, according to
[the specification][floatspec].
- `lyaml.load` now correctly reads a !!int tagged scalar from a
YAML document, or an implicit integer value, according to
[the specification][intspec].
- `lyaml.load` now supports the !!merge key type according to
[the specification][mergespec].
```yaml
- &MERGE { x: 1, y: 2 }
- &OVERRIDE { x: 0, z: 1 }
-
<< : [&MERGE, &OVERRIDE]
z: 3
```
The anchored tables remain in the document too, so this results in
the following Lua table:
```lua
{ -- START_STREAM
{ -- START_DOCUMENT
{ x = 1, y = 2 }, -- MERGE
{ x = 0, z = 1 }, -- OVERRIDE
{ x = 1, y = 2, z = 3}, -- <<<
} -- END_DOCUMENT
} -- END_STREAM
```
### Bug fixes
- Multi-line strings were previously being dumped using single quotes
which caused the dumped YAML to break.
For example, { foo = "a\nmultiline\nstring" } would get dumped as:
```yaml
foo: 'a
multiline
string'
```
Note the extra line-breaks in between each line. This also causes
YAML parsing to fail (since the blank lines didn't have the expected
indentation).
This patch fixes the dump to use the YAML literal syntax for any
multi-line strings so the same example gets dumped as:
```yaml
foo: |-
a
multiline
string
```
- `lyaml.load` now correctly reads the !!null tag in a YAML
document as an `lyaml.null` reference, identical to the "~"
shorthand syntax, according to [the specification][nullspec].
### Incompatible Changes
- `lyaml.load` now takes a table of options as an optional second
argument, not a simple boolean to determine whether all documents
should be returned from the stream. For now, a `true` second
argument will be converted to the modern equivalent:
```lua
lyaml.load (document, { all = true })
```
- `lyaml.dump` now takes a table of options as an optional second
argument, not an initial table of anchors. For now, a second
argument without any new API keys will be converted to the modern
equivalent:
```lua
lyaml.dump (t, { anchors = arg2 })
```
[boolspec]: http://yaml.org/type/bool.html
[floatspec]: http://yaml.org/type/float.html
[intspec]: http://yaml.org/type/int.html
[mergespec]: http://yaml.org/type/merge.html
[nullspec]: http://yaml.org/type/null.html
## Noteworthy changes in release 5.1.4 (2015-01-01) [stable]
- This release is functionally identical to the last.
## Noteworthy changes in release 5.1.3 (2015-01-01) [stable]
- This release is functionally identical to the last.
## Noteworthy changes in release 5.1.2 (2014-12-27) [stable]
### Bugs Fixed
- No more spurious .travis.yml is out of date warnings during
`luarocks install lyaml`.
## Noteworthy changes in release 5.1.1 (2014-12-19) [stable]
### Bugs Fixed
- When using `sudo make install` instead of LuaRocks, `lyaml.so`
is now correctly installed to `$luaexecdir`.
## Noteworthy changes in release 5.1.0 (2014-12-17) [stable]
### New Features
- Lua 5.3.0 compatibility.
## Noteworthy changes in release 5 (2014-09-25) [beta]
### Build
- Significantly reduced pointer mismatch warnings from modern GNU
compilers.
### New Features
- `lyaml.dump` now takes a second argument containing a table of
potential anchor values in `ANCHOR_NAME = { "match", "elements" }`
pairs format. The first time any are matched in the table being
dumped, they are preceded by `&ANCHOR_NAME` in the output YAML
document; subsequent matches are not written out in full, but
shortened to the appropriate `*ANCHOR_NAME` alias.
### Bugs Fixed
- `yaml.emitter` no longer emits numbers in SINGLE_QUOTE style by
default.
- `yaml.emitter ().emit` returns error strings correctly for invalid
STREAM_START encoding, and MAPPING_START, SEQUENCE_START & SCALAR
style fields.
## Noteworthy changes in release 4 (2013-09-11) [beta]
### New Features
- New yaml.emitter API returns an object with an emit method for
adding events using yaml_*_event_initialize() calls.
- New yaml.parser API returns a Lua iterator that fetches the next
event using yaml_parser_parse().
- New yaml.scanner API returns a Lua iterator that fetches the next
token using yaml_parser_scan().
- Beginnings of Specl specs, starting with a reasonably comprehensive
specifications for the new APIs above.
- C implementation of lyaml.dump has moved to Lua implementation as
yaml.dump.
- C implementation of lyaml.load has moved to Lua implementation as
yaml.load.
- The new Lua implementation of lyaml.load () handles multi-document
streams, and returns a table of documents when the new second
argument is `true`.
## Noteworthy changes in release 3 (2013-04-27) [beta]
- This release is functionally identical to the last.
### New Features
- lyaml builds are now made against Lua 5.1, Lua 5.2 and luajit 2.0.0
automatically, with every commit.
- move to a cleaner, automated release system.
## Noteworthy changes in release 2 (2013-03-18) [beta]
- This release is functionally identical to the last.
- Use correct MIT license attribution, relicensing build files to match
Andrew Danforth''s MIT licensed lyaml.c too.
## Noteworthy changes in release 1 (2013-03-17) [beta]
### New Features
- A binding for libYAML, by Andrew Danforth: Updated for Lua 5.1 and
5.2, and packaged as a luarock.
- I spun this out of Specl (http://github.com/gvvaughan/specl) so that
other projects may use it, and to simplify the Specl build.
### Known Issues
- There's not really any documentation, sorry. Contributions welcome!

232
README.md Normal file
View file

@ -0,0 +1,232 @@
LYAML
=====
Copyright (C) 2013-2022 Gary V. Vaughan
[![License](https://img.shields.io/:license-mit-blue.svg)](https://mit-license.org)
[![workflow status](https://github.com/gvvaughan/lyaml/actions/workflows/spec.yml/badge.svg?branch=release-v6.2.8)](https://github.com/gvvaughan/lyaml/actions)
[![codecov.io](https://codecov.io/github/gvvaughan/lyaml/coverage.svg?branch=release-v6.2.8)](https://codecov.io/github/gvvaughan/lyaml?branch=release-v6.2.8)
[LibYAML] binding for [Lua], with a fast C implementation
for converting between [%YAML 1.1][yaml11] and [Lua] tables,
and a low-level [YAML] event parser for implementing more
intricate [YAML] document loading.
Usage
-----
### High Level API
These functions quickly convert back and forth between Lua tables
and [%YAML 1.1][yaml11] format strings.
```lua
local lyaml = require "lyaml"
local t = lyaml.load (YAML-STRING, [OPTS-TABLE])
local yamlstr = lyaml.dump (LUA-TABLE, [OPTS-TABLE])
local null = lyaml.null ()
```
#### `lyaml.load`
`lyaml.load` accepts a YAML string for parsing. If the YAML string contains
multiple documents, only the first document will be returned by default. To
return multiple documents as a table, set `all = true` in the second
argument OPTS-TABLE.
```lua
lyaml.load("foo: bar")
--> { foo = "bar" }
lyaml.load("foo: bar", { all = true })
--> { { foo = "bar" } }
multi_doc_yaml = [[
---
one
...
---
two
...
]]
lyaml.load(multi_doc_yaml)
--> "one"
lyaml.load(multi_doc_yaml, { all = true })
--> { "one", "two" }
```
You can supply an alternative function for converting implicit plain
scalar values in the `implicit_scalar` field of the OPTS-TABLE argument;
otherwise a default is composed from the functions in the `lyaml.implicit`
module.
You can also supply an alternative table for coverting explicitly tagged
scalar values in the `explicit_scalar` field of the OPTS-TABLE argument;
otherwise all supported tags are parsed by default using the functions
from the `lyaml.explicit` module.
#### `lyaml.dump`
`lyaml.dump` accepts a table of values to dump. Each value in the table
represents a single YAML document. To dump a table of lua values this means
the table must be wrapped in another table (the outer table represents the
YAML documents, the inner table is the single document table to dump).
```lua
lyaml.dump({ { foo = "bar" } })
--> ---
--> foo: bar
--> ...
lyaml.dump({ "one", "two" })
--> --- one
--> ...
--> --- two
--> ...
```
If you need to round-trip load a dumped document, and you used a custom
function for converting implicit scalars, then you should pass that same
function in the `implicit_scalar` field of the OPTS-TABLE argument to
`lyaml.dump` so that it can quote strings that might otherwise be
implicitly converted on reload.
#### Nil Values
[Lua] tables treat `nil` valued keys as if they were not there,
where [YAML] explicitly supports `null` values (and keys!). Lyaml
will retain [YAML] `null` values as `lyaml.null ()` by default,
though it is straight forward to wrap the low level APIs to use `nil`,
subject to the usual caveats of how nil values work in [Lua] tables.
### Low Level APIs
```lua
local emitter = require ("yaml").emitter ()
emitter.emit {type = "STREAM_START"}
for _, event in ipairs (event_list) do
emitter.emit (event)
end
str = emitter.emit {type = "STREAM_END"}
```
The `yaml.emitter` function returns an emitter object that has a
single emit function, which you call with event tables, the last
`STREAM_END` event returns a string formatted as a [YAML 1.1][yaml11]
document.
```lua
local iter = require ("yaml").scanner (YAML-STRING)
for token_table in iter () do
-- process token table
end
```
Each time the iterator returned by `scanner` is called, it returns
a table describing the next token of YAML-STRING. See LibYAML's
[yaml.h] for details of the contents and semantics of the various
tokens produced by `yaml_parser_scan`, the underlying call made by
the iterator.
[LibYAML] implements a fast parser in C using `yaml_parser_scan`, which
is also bound to lyaml, and easier to use than the token API above:
```lua
local iter = require ("yaml").parser (YAML-STRING)
for event_table in iter () do
-- process event table
end
```
Each time the iterator returned by `parser` is called, it returns
a table describing the next event from the "Parse" process of the
"Parse, Compose, Construct" processing model described in the
[YAML 1.1][yaml11] specification using [LibYAML].
Implementing the remaining "Compose" and "Construct" processes in
[Lua] is left as an exercise for the reader -- though, unlike the
high-level API, `lyaml.parser` exposes all details of the input
stream events, such as line and column numbers.
Installation
------------
There's no need to download an [lyaml] release, or clone the git repo,
unless you want to modify the code. If you use [LuaRocks], you can
use it to install the latest release from its repository:
luarocks --server=http://rocks.moonscript.org install lyaml
Or from the rockspec in a release tarball:
luarocks make lyaml-?-1.rockspec
To install current git master from [GitHub][lyaml] (for testing):
luarocks install http://raw.github.com/gvvaughan/lyaml/master/lyaml-git-1.rockspec
To install without [LuaRocks], clone the sources from the
[repository][lyaml], and then run the following commands:
```sh
cd lyaml
build-aux/luke LYAML_DIR=LIBYAML-INSTALL-PREFIX
sudo build-aux/luke PREFIX=LYAML-INSTALL-PREFIX install
specl -v1freport spec/*_spec.yaml
```
The dependencies are listed in the dependencies entry of the file
[rockspec][L15].
Bug reports and code contributions
----------------------------------
This library is maintained by its users.
Please make bug reports and suggestions as [GitHub Issues][issues].
Pull requests are especially appreciated.
But first, please check that your issue has not already been reported by
someone else, and that it is not already fixed by [master][lyaml] in
preparation for the next release (see Installation section above for how
to temporarily install master with [LuaRocks][]).
There is no strict coding style, but please bear in mind the following
points when proposing changes:
0. Follow existing code. There are a lot of useful patterns and avoided
traps there.
1. 3-character indentation using SPACES in Lua sources: It makes rogue
TABs easier to see, and lines up nicely with 'if' and 'end' keywords.
2. Simple strings are easiest to type using single-quote delimiters,
saving double-quotes for where a string contains apostrophes.
3. Save horizontal space by only using SPACEs where the parser requires
them.
4. Use vertical space to separate out compound statements to help the
coverage reports discover untested lines.
5. Prefer explicit string function calls over object methods, to mitigate
issues with monkey-patching in caller environment.
[issues]: http://github.com/gvvaughas/lyaml/issues
[libyaml]: http://pyyaml.org/wiki/LibYAML
[lua]: http://www.lua.org
[luarocks]: http://www.luarocks.org
[lyaml]: http://github.com/gvvaughan/lyaml
[L15]: http://github.com/gvvaughan/lyaml/blob/master/lyaml-git-1.rockspec#L15
[yaml.h]: http://pyyaml.org/browser/libyaml/branches/stable/include/yaml.h
[yaml]: http://yaml.org
[yaml11]: http://yaml.org/spec/1.1/

34
build-aux/config.ld.in Normal file
View file

@ -0,0 +1,34 @@
--[[
LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
Copyright (C) 2013-2022 Gary V. Vaughan
]]
title = '@package@ @version@ Reference'
project = '@package@ @version@'
description = [[
# LYAML binding for Lua
This is a Lua binding for the fast libYAML C library for converting
between `%YAML 1.1` and Lua tables, with a flexible Lua language
API to load and save YAML documents.
It works with Lua 5.1 (including LuaJIT), 5.2, 5.3 and 5.4.
## LICENSE
The code is copyright by its respective authors, and released under the
MIT license (the same license as Lua itself). There is no warranty.
]]
dir = '../doc'
file = {
'../lib/lyaml/init.lua',
'../lib/lyaml/explicit.lua',
'../lib/lyaml/functional.lua',
'../lib/lyaml/implicit.lua',
}
format = 'markdown'
backtick_references = false
sort = false

672
build-aux/luke Executable file
View file

@ -0,0 +1,672 @@
#!/usr/bin/env lua
--[[ minified code follows, see --help text for source location! ]]
local require=function(modname)if package.loaded[modname]==nil then
if type(package.preload[modname])~="function"then
io.stderr:write("module '" .. modname .. "' not found:\n no valid field package.preload['" .. modname .. "']\n")
return nil
end
package.loaded[modname]=package.preload[modname](modname,"package.preload")end
return package.loaded[modname]end
package.preload['luke._base']=function()
local _ENV=require'std.normalize'{}local function fatal(...)local msg=(...)if select('#',...)>1 then
msg=format(...)end
stderr:write('luke: fatal: '..msg..'\n')exit(1)end
return{diagnose=function(predicate,...)if not predicate then
fatal(...)end
end,fatal=fatal,}
end
package.preload['luke.cli']=function()
local _ENV=require'std.normalize'{'luke._base','luke.lukefile','luke.platforms','std.functional',}local function version()print[[
luke (Luke) 0.2.3
Written by Gary V. Vaughan <gary@gnu.org>, 2014
Copyright (C) 2022, Gary V. Vaughan
Luke comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of Luke under the terms of the MIT license;
it may be used for any purpose at absolutely no cost, without permission.
See <https://mit-license.org> for details.
]]exit(0)end
local function help()print[[
Usage: luke [OPTION]... [VAR=VALUE]... [TARGET]
Use the source, Luke!
--help print this help, then exit
--version print version number, then exit
--file=FILE use FILE instead of lukefile
--value=NAME print the value of variable NAME
--quiet without any output
--verbose provide more progress output
Each TARGET can be one of the module table keys from lukefile, or:
all build all targets in lukefile
install copy all built targets to $PREFIX
If no TARGET is given, 'all' is implied.
Report bugs to https://github.com/gvvaughan/luke/issues.]]exit(0)end
local function opterr(...)local msg=(...)if select('#',...)>1 then
msg=format(...)end
msg=gsub(msg,'%.$','')stderr:write('luke: error: '..msg..'.\n')stderr:write("luke: try '"..arg[0].." --help' for help.\n")exit(2)end
local function display(...)return stdout:write(concat{...})end
local function dump(...)local s=concat(map(list(...),str))if len(s)>0 then
gsub(concat(map(list(...),str)),'\n*$','\n'):gsub('(.-)\n',function(line)stderr:write(' DEBUG: '..line..'\n')end)end
end
local function interpolate_to_substitute(s)return(gsub(s,'%$([%w_]+)','@%1@'))end
return{parse_arguments=function(args)local r={clidefs={},valreqs={},fname='lukefile',install={},log=nop,targets={},verbose=nop,write=display,}map(args,function(opt)case(opt,{['--debug']=function()r.log=dump
end,['%-%-file=(.+)']=function(optarg)r.fname=optarg
end,['%-%-value=(.+)']=function(optarg)r.valreqs[#r.valreqs+1]=optarg
end,['--quiet']=function()r.write=nop
end,['--verbose']=function()r.verbose=display
end,['--help']=help,['--version']=version,['([^-][^=]-)=(.+)']=function(name,value)r.clidefs[name]=value
end,function(opt)if match(opt,'^-')~=nil then
opterr("unrecognized option '%s'",opt)end
append(r.targets,opt)end,})end)return r
end,validate_arguments=function(parsed)local luke,err=loadluke(parsed.fname)diagnose(luke~=nil,'bad %s: %s',parsed.fname,err)if isempty(luke.modules or{})then
fatal("no modules table in '%s', nothing to build",parsed.fname)end
local targets=call(function()if isempty(parsed.targets)or contains(parsed.targets,'all')then
return except(flatten(parsed.targets,keys(luke.modules)),'all')end
local r=filter(parsed.targets,function(target)if target~='install'and luke.modules[target]==nil then
fatal("no rule to make target '%s'",target)end
return true
end)assert(len(r)>0,"no build targets specified")return r
end)local install
local build=pluck(targets,luke.modules)if contains(targets,'install')then
install=build or luke.modules
end
luke.modules=build
if isempty(luke.modules)then
luke.external_dependencies=nil
end
luke.substitute=merge(luke.substitute or{},{package=interpolate_to_substitute(luke.package),version=interpolate_to_substitute(luke.version),})luke.variables=merge(luke.variables or{},collect_variables(luke),{LUA_DIR='/usr',LUA_BINDIR='$LUA_DIR/bin',LUA_INCDIR='$LUA_DIR/include/lua$LUAVERSION',LUA_LIBDIR='$LUA_DIR/lib',objdir=platforms[1],package=luke.package,version=luke.version,})return{clidefs=parsed.clidefs,install=install,log=parsed.log,luke=luke,valreqs=parsed.valreqs,verbose=parsed.verbose,write=parsed.write,}end,}
end
package.preload['luke.compile']=function()
local _ENV=require'std.normalize'{'luke._base','luke.environment','std.functional','type.context-manager','type.path',SHELLMETACHARS='[%s%$"]',}local function spawn(env,...)local command=interpolate(env,concat({...},' '))return with(TmpFile(),TmpFile(),function(out,err)local pipe=concat{command,' >',out.filename,' 2>',err.filename,'; printf $?'}return tonumber(slurp(Pipe(pipe))),slurp(File(err.filename)),slurp(File(out.filename))end)end
local function run(L,env,command)L.write(interpolate(env,concat(command,' ')),'\n')local status,err,out=spawn(env,unpack(command))if status~=0 then
if L.write==nop then
stdout:write(concat(command,' ')..'\n')end
stderr:write(err..'\n')end
return status,out,err
end
local function defines(env,deftables)return zip_with(merge({},unpack(deftables)),function(name,value)local fmt=cond({[int(value)==1]='-D%s'},{[match(value,SHELLMETACHARS)~=nil]="-D%s='%s'"},{[true]='-D%s=%s'})return format(fmt,name,value)end)end
local function incdirs(...)return map(flatten(...),function(v)return'-I'..v
end)end
local function libdirs(...)return map(flatten(...),function(v)return'-L'..v
end)end
local function c_module_path(objdir,name)return format('%s/%s.$LIB_EXTENSION',objdir,gsub(name,'%.','/'))end
local function c_source(module,objdir)local path=gsub(module,'%.','/')local src=c_module_path(objdir,path)return src,(gsub('$INST_LIBDIR/'..path,'/[^/]+$',''))end
local function lua_source(module,src)local abspath='$INST_LUADIR/'..gsub(module,'%.','/')if match(src,'/init%.lua$')then
abspath=abspath..'/init'end
abspath=abspath..'.lua'return src,(gsub(abspath,'/[^/]+%.lua$',''))end
local function module_to_path(module,sources,objdir)return dropuntil(sources,function(source)return case(source,{['.*%.[ch]']=bind(c_source,{module,objdir}),['(.*%.[ch])%.in']=bind(c_source,{module,objdir}),['.*%.lua']=bind(lua_source,{module}),['(.*%.lua)%.in']=bind(lua_source,{module}),function(src)fatal("unsupported source type '%s'",src)end,})end)end
return{build_c_module=function(L,env,luke,name)local rules=luke.modules[name]local c_module=c_module_path(luke.variables.objdir,name)local command={'$MAKEDIRS',dirname(c_module)}local status,err,out=spawn(env,unpack(command))if status~=0 then
stdout:write(concat(command,' ')..'\n')stderr:write(err..'\n')exit(status)end
return run(L,env,flatten('$CC $CFLAGS $LIBFLAG $PKGFLAGS $CPPFLAGS',defines(env,except(list(rules.defines,luke.defines),nil)),incdirs(rules.incdirs,luke.incdirs),rules.sources,'-o',c_module,'$LDFLAGS',libdirs(rules.libdirs,luke.libdirs),'$LIBS',rules.libraries,luke.libraries))end,c_modules=function(modules)return filter(keys(modules),function(name)return dropuntil(modules[name].sources,bind(match,{[2]='%.[ch]$'}))end)end,incdirs=incdirs,install_modules=function(L,env,luke,modules)return reduce(keys(modules),0,function(status,name)if status==0 then
local src,dir=module_to_path(name,modules[name].sources,luke.variables.objdir)if not exists(interpolate(env,dir))then
status=run(L,env,{'$MAKEDIRS',dir})end
if status==0 then
status=run(L,env,{'$INSTALL',src,dir..'/'})end
end
return status
end)end,libdirs=libdirs,run_command=run,spawn=spawn,}
end
package.preload['luke.configure']=function()
local _ENV=require'std.normalize'{'luke._base','luke.compile','luke.environment','std.functional','type.context-manager','type.dict',CCPROGS={'cc','gcc','clang'},}local function logspawn(L,env,...)local status,err=spawn(env,...)if status~=0 and err~=''then
L.log(err)end
return status
end
local function checking(L,...)L.verbose('checking ',concat({...},' '),'... ')end
local function found_library(L,x)if x==nil or x==''then
L.verbose'none required'elseif isempty(x)then
L.verbose'not supported'else
L.verbose(x)end
L.verbose'\n'return x
end
local function found_prog(L,x)L.verbose(x and'yes\n'or'no\n')return x
end
local function found_result(L,x)L.verbose(x==0 and'yes\n'or'no\n')return x~=0 and 0 or 1
end
local function bindirs(...)return map(flatten(...),function(v)return v..':'end)end
local function compile_command(L,env,config,filename)local command=flatten('$CC','-c','$CFLAGS',incdirs(config.incdir),'$CPPFLAGS',filename)L.log(interpolate(env,concat(command,' ')))return unpack(command)end
local function link_command(L,env,config,a_out,source,lib)local command=flatten('$CC','$CFLAGS',incdirs(config.incdir),'$CPPFLAGS','-o',a_out,source,libdirs(config.libdir),'$LDFLAGS',lib,'$libs',CONFIGENV.libs)L.log(interpolate(env,concat(command,' ')))return unpack(command)end
local function check_executable_in_path(L,env,config,prog)local PATH=concat(bindirs(config.bindir))..getenv('PATH')local found=dropuntil(gmatch(PATH,'[^:]+'),function(path)local progpath=path..'/'..prog
return with(File(progpath,'r'),function(h)return h and isfile(h.context)and progpath or nil
end)end)L.log(found and'found '..found or prog..' not found')return found~=nil
end
local function check_header_compile(L,env,config,header,extra_hdrs)return with(CTest(),function(conftest)conftest:write(format('%s\n#include "%s"\n',extra_hdrs,header))return logspawn(L,env,compile_command(L,env,config,conftest.filename))end)end
local function check_struct_member_compile(L,env,config,structname,member,extra_hdrs)return with(CTest(),function(conftest)conftest:write(format([[
%s
int main () {
static %s aggr;
if (sizeof aggr.%s)
return 0;
return 0;
}
]],extra_hdrs,structname,member))return logspawn(L,env,compile_command(L,env,config,conftest.filename))end)end
local function try_link(L,env,config,lib,symbol)return with(CTest(),TmpFile(),function(conftest,a_out)conftest:write(format([[
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
char %s ();
int main () {
return %s ();
}
]],symbol,symbol))return logspawn(L,env,link_command(L,env,config,a_out.filename,conftest.filename,lib))end)end
local function try_compile(L,env,config,headers)return with(CTest(),TmpFile(),function(conftest,a_out)conftest:write(format([[
%s
#if !defined %s || %s == -1
choke me
#endif
int
main()
{
return 0;
}
]],headers,config.ifdef,config.ifdef))return logspawn(L,env,link_command(L,env,config,a_out.filename,conftest.filename))end)end
local function check_func_decl(L,env,config,fname,extra_hdrs)return with(CTest(),function(conftest)conftest:write(format([[
%s
int
main()
{
#ifndef %s
(void) %s;
#endif
return 0;
}
]],extra_hdrs,fname,fname))return logspawn(L,env,compile_command(L,env,config,conftest.filename))end)end
local function check_func_link(L,env,config,fname)return with(CTest(),TmpFile(),function(conftest,a_out)conftest:write(format([[
/* Define to an innocous variant, in case <limits.h> declares it.
For example, HP-UX 11i <limits,h> declares gettimeofday. */
#define %s innocuous_%s
/* System header to define __stub macros and hopefully few prototypes,
which can conflict with declaration below.
Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
<limits.h> exists even on freestanding compilers. */
#ifdef __STDC__
# include <limits.h>
#else
# include <assert.h>
#endif
#undef %s
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
char %s ();
/* The GNU C library defines this for functions which it implements
to always fail with ENOSYS. Some functions are actually named
something starting with __ and the normal name is an alias. */
#if defined __stub_%s || defined __stub__%s
choke me
#endif
int main () {
return %s ();
}
]],fname,fname,fname,fname,fname,fname,fname))return logspawn(L,env,link_command(L,env,config,a_out.filename,conftest.filename))end)end
local function add_external_deps(env,config,prefix)if prefix~=nil then
for k,v in next,{bindir='$%s_BINDIR',incdir='$%s_INCDIR',libdir='$%s_LIBDIR'}do
local envvar=interpolate(env,format(v,prefix))if envvar~=''then
config[k]=envvar
end
end
end
end
local function format_includes(includes)return map(includes or{},function(include)return format('#include "%s"',include)end)end
local configure=setmetatable(OrderedDict({checkprog=function(L,env,config)return dropuntil(config.progs,function(prog)checking(L,'for',prog)if found_prog(L,check_executable_in_path(L,env,config,prog))then
return prog
end
end)or fatal('cannot find '..config.checkprog)end},{checkheader=function(L,env,config)checking(L,'for',config.checkheader)local extra_hdrs=concat(format_includes(config.includes),'\n')return found_result(L,check_header_compile(L,env,config,config.checkheader,extra_hdrs))end},{checkdecl=function(L,env,config)checking(L,'whether',config.checkdecl,'is declared')local extra_hdrs=concat(format_includes(config.includes),'\n')return found_result(L,check_func_decl(L,env,config,config.checkdecl,extra_hdrs))end},{checksymbol=function(L,env,config)checking(L,'for library containing',config.checksymbol)if config.ifdef~=nil then
local headers=concat(format_includes(config.includes),'\n')if try_compile(L,env,config,headers)~=0 then
return found_library(L,{})end
end
local libraries,symbol=config.libraries,config.checksymbol
local trylibs=reduce(libraries,{''},function(r,lib)append(r,'-l'..lib)end)return dropuntil(trylibs,function(lib)if try_link(L,env,config,lib,symbol)==0 then
if lib~=''then
if CONFIGENV.libs~=''then
CONFIGENV.libs=' '..CONFIGENV.libs
end
CONFIGENV.libs=lib..CONFIGENV.libs
end
return found_library(L,lib)end
end)or call(function()L.verbose'\n'fatal("required symbol '%s' not found in any of libc, lib%s",symbol,concat(libraries,', lib'))end)end},{checkfunc=function(L,env,config)checking(L,'for',config.checkfunc)return found_result(L,check_func_link(L,env,config,config.checkfunc))end},{checkmember=function(L,env,config)checking(L,'for',config.checkmember)local extra_hdrs=concat(format_includes(config.includes),'\n')local i=find(config.checkmember,'%.')local structname=sub(config.checkmember,1,i-1)local member=sub(config.checkmember,i+1)return found_result(L,check_struct_member_compile(L,env,config,structname,member,extra_hdrs))end}),{__call=function(self,L,env,config,prefix)return case(type(config),{['number']=function()return str(config)end,['string']=function()return config
end,['table']=function()return dropuntil(self,function(fname)if config[fname]~=nil then
add_external_deps(env,config,prefix)return apply(self[fname],list(L,env,config))end
end)or fatal("unable to configure with keys '%s'",concat(keys(config),"', '"))end,function(type)fatal("unsupported configure type '%s'",type)end,})end,})return{config_compiler=function(L,env)local CC=env.CC
if CC==nil then
CC=configure(L,env,{checkprog='C compiler',progs=CCPROGS})env=makeenv(env,{CC=CC})end
checking(L,interpolate(env,'whether $CC works'))local cm=CTest()local works,err=with(cm,function(conftest)conftest:write('typedef int x;\n')return spawn(env,'$compile',conftest.filename)end)if works~=0 then
L.verbose'no\n'L.log(interpolate(env,'$compile '..cm.filename))if err and err~=''then
L.log(err)end
fatal('could not find a working C compiler')end
found_prog(L,CC)return env
end,config_ldoc=function(L,env)local LDOC=env.LDOC
if LDOC==nil then
LDOC=configure(L,env,{checkprog='LDocs generator',progs={'ldoc','true'}})env=makeenv(env,{LDOC=LDOC})end
return env
end,configure=configure,}
end
package.preload['luke.environment']=function()
local _ENV=require'std.normalize'{'luke.platforms','std.functional',LUAVERSION=string.gsub(_VERSION,'[^0-9%.]+',''),}local env_mt={__index=function(self,varname)return dropuntil(self,function(env)local value=env[varname]if value~=nil then
self[varname]=value
return value
end
end)end,}local function interpolate_with(pattern,env,s)local r=''while r~=s do
r=s
s=gsub(r,pattern,function(varname)return env[varname]or''end)end
return r
end
local function isenv(t)return getmetatable(t)==env_mt
end
return{CONFIGENV={compile='$CC -c $CFLAGS $CPPFLAGS',libs='',link='$CC $CFLAGS $CPPFLAGS $LDFLAGS',},DEFAULTENV=filter_platforms{LUAVERSION=LUAVERSION,PREFIX='/usr/local',INST_LIBDIR='$PREFIX/lib/lua/$LUAVERSION',INST_LUADIR='$PREFIX/share/lua/$LUAVERSION',LIB_EXTENSION='so',OBJ_EXTENSION='o',INSTALL='cp',MAKEDIRS='mkdir -p',CFLAGS='-O2',platforms={macosx={LIBFLAG='-fPIC -bundle -undefined dynamic_lookup -all_load',},LIBFLAG='-shared -fPIC',},},SHELLENV=setmetatable({},{__index=function(_,v)return getenv(v)end,}),expand=bind(interpolate_with,{'@([^@]+)@'}),interpolate=bind(interpolate_with,{'%$([%w_]+)'}),makeenv=function(...)local env=reduce(except(list(...),nil),function(r,t)if isenv(t)then
map(t,bind(append,{r}))else
append(r,t)end
end)return setmetatable(env,env_mt)end,}
end
package.preload['luke']=function()
local _ENV=require'std.normalize'{'luke.cli','luke.compile','luke.configure','luke.environment','luke.lukefile','std.functional',}local function run_ldocs(L,env,ldocs)return run_command(L,env,flatten{'$LDOC -c',ldocs.sources,'.'})end
local function build_modules(L,env)local conf=makeenv(CONFIGENV,env)if not isempty(L.luke.ldocs or{})then
conf=config_ldoc(L,conf)env=makeenv(env,{LDOC=conf.LDOC})end
local c=c_modules(L.luke.modules)if not isempty(c)then
conf=config_compiler(L,conf)env=makeenv(env,{CC=conf.CC})end
L.luke=run_configs(L,conf,L.luke)local substitute=makeenv(L.clidefs,L.luke.substitute,SHELLENV)L.luke=run_templates(L,substitute,L.luke)local status=dropuntil(c,isnonzero,function(name)return build_c_module(L,env,L.luke,name)end)or 0
if status==0 and not isempty(L.luke.ldocs or{})then
status=run_ldocs(L,env,L.luke.ldocs)end
return status
end
return{main=function(args)local L=validate_arguments(parse_arguments(args))local env=makeenv(L.clidefs,L.luke.variables,DEFAULTENV,SHELLENV)local status=0
if not isempty(L.valreqs)then
map(L.valreqs,function(name)print(interpolate(env,concat{name,"='$",name,"'"}))end)exit(0)end
if status==0 and not isempty(L.luke.modules or{})then
status=build_modules(L,env)end
if status==0 then
status=install_modules(L,env,L.luke,L.install)end
return status
end,}
end
package.preload['luke.lukefile']=function()
local _ENV=require'std.normalize'{'luke._base','luke.configure','luke.environment','luke.platforms','std.functional','type.context-manager',}local function has_anykey(t,keylist)return any(map(keylist,function(k)return t[k]~=nil
end))end
local function isconfig(x)return istable(x)and has_anykey(x,configure)end
local function collect_configs(luke,modulename,configs)configs=configs or{}for k,v in next,luke do
if isconfig(v)then
append(configs,{t=luke,k=k,module=modulename})elseif istable(v)then
if k=='modules'or k=='external_dependencies'then
for name,rules in next,v do
collect_configs(rules,name,configs)end
else
collect_configs(v,modulename,configs)end
end
end
return configs
end
local function deepcopy(t)return mapvalues(t,function(v)return case(type(v),{['table']=function()return deepcopy(v)end,v,})end)end
local weighting=setmetatable(copy(configure),{__call=function(self,config)local t=config.t[config.k]for i=1,len(self)do
if t[self[i]]~=nil then
return i
end
end
end})local function config_cmp(a,b)return weighting(a)<weighting(b)end
local function fill_templates(env,src,dest)with(File(dest,'w'),function(cm)for line in lines(src)do
cm:write(expand(env,line)..'\n')end
end)return dest
end
local function rewrite_template_files(L,env,source)return case(source,{['(.+)%.in']=function(r)L.write('creating '..r..'\n')return fill_templates(env,r..'.in',r)end,source,})end
local function collect_variables(luke,variables)for k,v in next,luke do
if k=='external_dependencies'then
map(keys(v),function(name)local rootdir=concat{'$',name,'_DIR'}variables[name..'_DIR']='/usr'variables[name..'_BINDIR']=rootdir..'/bin'variables[name..'_INCDIR']=rootdir..'/include'variables[name..'_LIBDIR']=rootdir..'/lib'end)elseif istable(v)then
collect_variables(v,variables)end
end
return variables
end
local function normalize_configs(config)return cond({[not istable(config)]=config,},{[not isconfig(config)]=function()return mapvalues(config,normalize_configs)end,},{[true]=function()local keymap={include='includes',prog='progs',library='libraries',}return foldkeys(keymap,config,function(a,b)local r=istable(a)and copy(a)or{a}b=istable(b)and b or{b}return reduce(b,r,function(v)append(r,v)end)end)end,})end
local function normalize_rules(rules)return case(type(rules),{['nil']=nop,['string']=function()return{sources={rules}}end,['table']=function()if len(rules)>0 then
return{sources=rules}elseif isstring(rules.sources)then
return merge({sources={rules.sources}},normalize_configs(rules))end
return normalize_configs(rules)end,function(v)fatal("unsupported rule type '%s'",v)end,})end
local function unwrap_external_dependencies(luke)if istable(luke.external_dependencies)then
for prefix,config in next,luke.external_dependencies do
if istable(config)and next(config)and config.library~=''then
luke.incdirs=append(luke.incdirs or{},format('$%s_INCDIR',prefix))luke.libdirs=append(luke.libdirs or{},format('$%s_LIBDIR',prefix))luke.libraries=append(luke.libraries or{},config.library)end
end
luke.external_dependencies=nil
end
return luke
end
return{loadluke=function(filename)local content,err=slurp(File(filename))if content==nil then
return nil,err
end
local r={}local chunk,err=loadstring(content,filename,r)if chunk==nil then
return nil,"Error loading file: "..err
end
local ok,err=pcall(chunk)if not ok then
return nil,"Error running file: "..err
end
r=filter_platforms(r)r.external_dependencies=normalize_configs(r.external_dependencies)r.ldocs=normalize_rules(r.ldocs)r.modules=mapvalues(r.modules,normalize_rules)return r
end,collect_variables=function(luke)return collect_variables(luke,{})end,run_configs=function(L,env,luke)local r=deepcopy(luke)local all_configs=collect_configs(r)sort(all_configs,config_cmp)map(all_configs,function(config)config.t[config.k]=configure(L,env,config.t[config.k],config.module)end)return unwrap_external_dependencies(r)end,run_templates=function(L,env,luke)local r=copy(luke)local rewrite=bind(rewrite_template_files,{L,env})r.modules=mapvalues(r.modules,function(rules)rules.sources=map(rules.sources,rewrite)end)if r.ldocs then
r.ldocs.sources=map(r.ldocs.sources,rewrite)end
return r
end,}
end
package.preload['luke.platforms']=function()
local _ENV=require'std.normalize'{'std.functional',}local CANON={['AIX']=list('aix','unix'),['FreeBSD']=list('freebsd','bsd','unix'),['OpenBSD']=list('openbsd','bsd','unix'),['NetBSD']=list('netbsd','bsd','unix'),['Darwin']=list('macosx','bsd','unix'),['Linux']=list('linux','unix'),['SunOS']=list('solaris','unix'),['^CYGWIN']=list('cygwin','unix'),['^MSYS']=list('msys','cygwin','unix'),['^Windows']=list('win32','windows'),['^MINGW']=list('mingw32','win32','windows'),['^procnto']=list('qnx'),['QNX']=list('qnx'),['Haiku']=list('haiku','unix'),}local ALLPLATFORMS=reduce(values(CANON),function(acc,platforms)map(platforms,function(v)acc[v]=true
end)end)local function match_uname(canon,uname,x)return match(uname,x)and canon[x]end
local function toplatforms(canon,uname)local literalkeys,patternkeys=partition(keys(canon),function(k)return sub(k,1,1)~='^'end)return(pluck(literalkeys,canon)or{})[uname]or dropuntil(map(patternkeys,bind(match_uname,{canon,uname})))or list('unix')end
local supported=toplatforms(CANON,popen('uname -s'):read'*l')local function isplatform(x)return ALLPLATFORMS[x]~=nil
end
local function filter_platforms(t,using,predicate)local r,supported,isplatform={},using or supported,predicate or isplatform
for k,v in next,t do
if k=='platforms'then
local matches=filter(supported,bind(get,{v}))local default=except(keys(v),isplatform)merge(r,hoist(matches,v)or pluck(default,v))elseif istable(v)then
r[k]=filter_platforms(v,supported)else
r[k]=r[k]or v
end
end
return r
end
return{filter_platforms=filter_platforms,platforms=supported,toplatforms=toplatforms,}
end
package.preload['std.functional']=function()
local _ENV=require'std.normalize'{destructure=next,isfile=function(x)return io.type(x)=='file'end,wrap=coroutine.wrap,yield=coroutine.yield,}local function apply(fn,argu)assert(fn~=nil,'cannot apply nil-valued function')if iscallable(fn)then
return fn(unpack(argu))end
return fn
end
local function call(fn,...)assert(fn~=nil,'cannot call nil-valued function')if iscallable(fn)then
return fn(...)end
return fn
end
local function wrapnonnil(iterator)return function(...)local r=list(iterator(...))if r[1]~=nil then
return r
end
end
end
local function each(seq)if type(seq)=='function'then
return wrapnonnil(seq)end
local i,n=0,int(seq.n)or len(seq)return function()if i<n then
i=i+1
return list(seq[i])end
end
end
local function eq(x)return function(y)return x==y
end
end
local function isnonnil(x)return x~=nil
end
local function mkpredicate(x)return type(x)=='function'and x or eq(x)end
local function except(seq,predicate)predicate=mkpredicate(predicate)local r={}for valu in each(seq)do
if not predicate(unpack(valu))then
r[#r+1]=unpack(valu)end
end
return r
end
local function visit(x)if type(x)=='table'then
for valu in each(x)do
visit(unpack(valu))end
else
yield(x)end
end
local function flatten(...)local r={}for v in wrap(visit),except(list(...),nil)do
r[#r+1]=v
end
return r
end
return{any=function(seq)for valu in each(seq)do
if unpack(valu)then
return true
end
end
return false
end,apply=apply,bind=function(fn,bound)local n=bound.n or maxn(bound)return function(...)local argu,unbound=copy(bound),list(...)local i=1
for j=1,unbound.n do
while argu[i]~=nil do
i=i+1
end
argu[i],i=unbound[j],i+1
end
bound.n=n>=i and n or i-1
return apply(fn,argu)end
end,call=call,case=function(s,branches)if branches[s]~=nil then
return call(branches[s],s)end
local DEFAULT=1
for pattern,fn in next,branches do
if pattern~=DEFAULT then
local argu=list(match(s,'^'..pattern..'$'))if argu[1]~=nil then
return apply(fn,argu)end
end
end
local default=branches[DEFAULT]if iscallable(default)then
return call(default,s)end
return default
end,cond=function(...)for clauseu in each(list(...))do
local expr,consequence=destructure(unpack(clauseu))if expr then
return call(consequence,expr)end
end
end,contains=function(seq,predicate)if type(predicate)~='function'then
predicate=eq(predicate)end
for valu in each(seq)do
if predicate(unpack(valu))then
return true
end
end
end,destructure=destructure,dropuntil=function(seq,predicate,block)if block==nil then
predicate,block=isnonnil,predicate
end
if block~=nil then
for valu in each(seq)do
local r=list(block(unpack(valu)))if predicate(unpack(r))then
return unpack(r)end
end
else
for r in each(seq)do
if predicate(unpack(r))then
return unpack(r)end
end
end
end,except=except,filter=function(seq,predicate)predicate=mkpredicate(predicate)local r={}for valu in each(seq)do
if predicate(unpack(valu))then
r[#r+1]=unpack(valu)end
end
return r
end,flatten=flatten,foldkeys=function(keymap,dict,combinator)local r={}for k,v in next,dict or{}do
local key=keymap[k]if key then
r[key]=combinator(v,dict[key])else
r[k]=r[k]or v
end
end
return r
end,get=function(dict,key)return(dict or{})[key]end,hoist=function(keylist,dict)local r={}for keyu in each(keylist)do
merge(r,dict[unpack(keyu)])end
return next(r)and r or nil
end,id=function(...)return...end,isempty=function(x)return type(x)=='table'and not next(x)end,isfile=isfile,isfunction=function(x)return type(x)=='function'end,isnil=function(x)return x==nil
end,isstring=function(x)return type(x)=='string'end,istable=function(x)return type(x)=='table'end,isnonzero=function(x)return x~=0
end,keys=function(iterable)local r=list()for k in next,iterable or{}do
append(r,k)end
return r
end,map=function(seq,block)local r=list()for valu in each(seq)do
append(r,block(unpack(valu)))end
return r
end,mapvalues=function(iterable,block)local r={}for k,v in next,iterable or{}do
r[k]=block(v)or v
end
return r
end,nop=function()end,partition=function(seq,block)local r,s=list(),list()for valu in each(seq)do
append(block(unpack(valu))and r or s,unpack(valu))end
return r,s
end,pluck=function(keylist,dict)local r={}for keyu in each(keylist)do
local key=unpack(keyu)r[key]=dict[key]end
return next(r)and r or nil
end,reduce=function(seq,acc,block)if block==nil then
acc,block={},acc
end
for valu in each(seq)do
acc=block(acc,unpack(valu))or acc
end
return acc
end,values=function(iterable)local r=list()for _,v in next,iterable or{}do
append(r,v)end
return r
end,zip_with=function(iterable,block)local r=list()for k,v in next,iterable or{}do
append(r,block(k,v))end
return r
end,}
end
package.preload['std.normalize']=function()
local ceil=math.ceil
local concat=table.concat
local config=package.config
local getmetatable=getmetatable
local loadstring=loadstring
local match=string.match
local next=next
local pack=table.pack or function(...)return{n=select('#',...),...}end
local setfenv=setfenv
local sort=table.sort
local tointeger=math.tointeger
local tonumber=tonumber
local tostring=tostring
local type=type
local unpack=table.unpack or unpack
local dirsep,pathsep,pathmark,execdir,igmark=match(config,'^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)')local function copy(iterable)local r={}for k,v in next,iterable or{}do
r[k]=v
end
return r
end
local int=(function(f)if f==nil then
return function(x)if type(x)=='number'and ceil(x)-x==0.0 then
return x
end
end
elseif f'1'~=nil then
return function(x)if type(x)=='number'then
return tointeger(x)end
end
end
return f
end)(tointeger)local function iscallable(x)return type(x)=='function'and x or(getmetatable(x)or{}).__call
end
local function getmetamethod(x,n)return iscallable((getmetatable(x)or{})[tostring(n)])end
local function rawlen(x)if type(x)~='table'then
return#x
end
local n=#x
for i=1,n do
if x[i]==nil then
return i-1
end
end
return n
end
local function len(x)local m=getmetamethod(x,'__len')return m and m(x)or rawlen(x)end
if setfenv then
local _loadstring=loadstring
loadstring=function(s,filename,env)chunk,err=_loadstring(s,filename)if chunk~=nil and env~=nil then
setfenv(chunk,env)end
return chunk,err
end
else
loadstring=function(s,filename,env)return load(s,filename,"t",env)end
setfenv=function()end
end
local function keysort(a,b)if int(a)then
return int(b)==nil or a<b
else
return int(b)==nil and tostring(a)<tostring(b)end
end
local function str(x,roots)roots=roots or{}local function stop_roots(x)return roots[x]or str(x,copy(roots))end
if type(x)~='table'or getmetamethod(x,'__tostring')then
return tostring(x)else
local buf={'{'}roots[x]=tostring(x)local n,keys=1,{}for k in next,x do
keys[n],n=k,n+1
end
sort(keys,keysort)local kp
for _,k in next,keys do
if kp~=nil and k~=nil then
buf[#buf+1]=type(kp)=='number'and k~=kp+1 and'; 'or', 'end
if k==1 or type(k)=='number'and k-1==kp then
buf[#buf+1]=stop_roots(x[k])else
buf[#buf+1]=stop_roots(k)..'='..stop_roots(x[k])end
kp=k
end
buf[#buf+1]='}'return concat(buf)end
end
return setmetatable({append=function(seq,v)local n=(int(seq.n)or len(seq))+1
seq.n,seq[n]=n,v
return seq
end,arg=arg,assert=assert,char=string.char,close=io.close,concat=concat,copy=copy,dirsep=dirsep,exit=os.exit,find=string.find,format=string.format,getenv=os.getenv,getmetatable=getmetatable,getmetamethod=getmetamethod,gmatch=string.gmatch,gsub=string.gsub,int=int,iscallable=iscallable,len=len,lines=io.lines,list=pack,loadstring=loadstring,match=string.match,maxn=function(iterable)local n=0
for k,v in next,iterable or{}do
local i=int(k)if i and i>n then
n=i
end
end
return n
end,merge=function(r,...)local argu=pack(...)for i=1,argu.n do
for k,v in next,argu[i]or{}do
r[k]=r[k]or v
end
end
return r
end,next=next,open=io.open,pack=pack,pcall=pcall,pop=function(seq)local n,r=seq.n or len(seq)r,seq[n]=seq[n]if int(seq.n)and seq.n>0 then
seq.n=seq.n-1
end
return r
end,popen=io.popen,print=print,rawget=rawget,rawset=rawset,rep=string.rep,rm=os.remove,select=select,setmetatable=setmetatable,sort=sort,stderr=io.stderr,stdout=io.stdout,str=str,sub=string.sub,tmpname=os.tmpname,tonumber=tonumber,type=type,unpack=function(seq,i,j)return unpack(seq,int(i)or 1,int(j)or int(seq.n)or len(seq))end,write=io.write,},{__call=function(self,env,level)local userenv,level=copy(self),level or 1
for name,value in next,env do
if int(name)and type(value)=='string'then
for k,v in next,(require(value))do
userenv[k]=userenv[k]or v
end
else
userenv[name]=value
end
end
setfenv(level+1,userenv)return userenv
end,})
end
package.preload['type.context-manager']=function()
local _ENV=require'std.normalize'{'std.functional',}local contextmanager_mt={__index=function(self,key)if iscallable(self.context[key])then
return function(_,...)return self.context[key](self.context,...)end
end
if key=='filename'then
return self[1]end
end,}local function ContextManager(release,acquire,...)local fh,err=acquire(...)if not fh then
return nil,err
end
local cm={context=fh,release=release,n=select("#",...),...}if cm.context~=nil then
setmetatable(cm,contextmanager_mt)end
return cm
end
local function context_close(cm)return isfile(cm.context)and close(cm.context)end
local function with(...)local argu=list(...)local block=pop(argu)local r=list(apply(block,argu))map(argu,function(cm)if cm~=nil then
cm:release()end
end)return unpack(r)end
return{ContextManager=ContextManager,CTest=function()local conftest=tmpname()return ContextManager(function(cm)rm(conftest)rm(gsub(conftest,'^.*/','')..'.o')if context_close(cm)then
return rm(cm.filename)end
return false
end,open,conftest..'.c','w')end,File=function(fname,mode)return ContextManager(context_close,open,fname,mode)end,Pipe=function(cmd,mode)return ContextManager(context_close,popen,cmd,mode)end,TmpFile=function(fname,mode)return ContextManager(function(cm)if context_close(cm)then
return rm(cm.filename)end
return false
end,open,fname or tmpname(),mode or'w')end,slurp=function(cm,...)if not cm then
return cm,...end
return with(cm,function(h)return h:read'*a'end)end,with=with,}
end
package.preload['type.dict']=function()
local _ENV=require'std.normalize'{destructure=next,}return{OrderedDict=function(...)local r,argu={},list(...)for i=1,argu.n do
local k,v=destructure(argu[i])append(r,k)r[k]=v
end
return r
end,}
end
package.preload['type.path']=function()
local _ENV=require'std.normalize'{}local BASENAMEPAT='.*'..dirsep
local DIRNAMEPAT=dirsep..'[^'..dirsep..']*$'return{basename=function(path)return(gsub(path,BASENAMEPAT,''))end,dirname=function(path)return(gsub(path,DIRNAMEPAT,'',1))end,exists=function(path)local fh=open(path)if fh==nil then
return false
end
close(fh)return true
end,}
end
os.exit(require'luke'.main(arg))

97
doc/index.html Normal file
View file

@ -0,0 +1,97 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head>
<title>lyaml 6.2.8 Reference</title>
<link rel="stylesheet" href="ldoc.css" type="text/css" />
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo"></div>
<div id="product_name"><big><b></b></big></div>
<div id="product_description"></div>
</div> <!-- id="product" -->
<div id="main">
<!-- Menu -->
<div id="navigation">
<br/>
<h1>lyaml 6.2.8</h1>
<h2>Modules</h2>
<ul class="nowrap">
<li><a href="modules/lyaml.html">lyaml</a></li>
<li><a href="modules/lyaml.explicit.html">lyaml.explicit</a></li>
<li><a href="modules/lyaml.functional.html">lyaml.functional</a></li>
<li><a href="modules/lyaml.implicit.html">lyaml.implicit</a></li>
</ul>
</div>
<div id="content">
<h2>
<h1>LYAML binding for Lua</h1>
<p>This is a Lua binding for the fast libYAML C library for converting
between <code>%YAML 1.1</code> and Lua tables, with a flexible Lua language
API to load and save YAML documents.</p>
<p>It works with Lua 5.1 (including LuaJIT), 5.2, 5.3 and 5.4.</p>
<h2>LICENSE</h2>
<p>The code is copyright by its respective authors, and released under the
MIT license (the same license as Lua itself). There is no warranty.</p>
</h2>
<h2>Modules</h2>
<table class="module_list">
<tr>
<td class="name" nowrap><a href="modules/lyaml.html">lyaml</a></td>
<td class="summary">
</td>
</tr>
<tr>
<td class="name" nowrap><a href="modules/lyaml.explicit.html">lyaml.explicit</a></td>
<td class="summary">
</td>
</tr>
<tr>
<td class="name" nowrap><a href="modules/lyaml.functional.html">lyaml.functional</a></td>
<td class="summary">
</td>
</tr>
<tr>
<td class="name" nowrap><a href="modules/lyaml.implicit.html">lyaml.implicit</a></td>
<td class="summary">
</td>
</tr>
</table>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-22 17:12:03 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

303
doc/ldoc.css Normal file
View file

@ -0,0 +1,303 @@
/* BEGIN RESET
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.com/yui/license.html
version: 2.8.2r1
*/
html {
color: #000;
background: #FFF;
}
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
fieldset,img {
border: 0;
}
address,caption,cite,code,dfn,em,strong,th,var,optgroup {
font-style: inherit;
font-weight: inherit;
}
del,ins {
text-decoration: none;
}
li {
margin-left: 20px;
}
caption,th {
text-align: left;
}
h1,h2,h3,h4,h5,h6 {
font-size: 100%;
font-weight: bold;
}
q:before,q:after {
content: '';
}
abbr,acronym {
border: 0;
font-variant: normal;
}
sup {
vertical-align: baseline;
}
sub {
vertical-align: baseline;
}
legend {
color: #000;
}
input,button,textarea,select,optgroup,option {
font-family: inherit;
font-size: inherit;
font-style: inherit;
font-weight: inherit;
}
input,button,textarea,select {*font-size:100%;
}
/* END RESET */
body {
margin-left: 1em;
margin-right: 1em;
font-family: arial, helvetica, geneva, sans-serif;
background-color: #ffffff; margin: 0px;
}
code, tt { font-family: monospace; font-size: 1.1em; }
span.parameter { font-family:monospace; }
span.parameter:after { content:":"; }
span.types:before { content:"("; }
span.types:after { content:")"; }
.type { font-weight: bold; font-style:italic }
body, p, td, th { font-size: .95em; line-height: 1.2em;}
p, ul { margin: 10px 0 0 0px;}
strong { font-weight: bold;}
em { font-style: italic;}
h1 {
font-size: 1.5em;
margin: 20px 0 20px 0;
}
h2, h3, h4 { margin: 15px 0 10px 0; }
h2 { font-size: 1.25em; }
h3 { font-size: 1.15em; }
h4 { font-size: 1.06em; }
a:link { font-weight: bold; color: #004080; text-decoration: none; }
a:visited { font-weight: bold; color: #006699; text-decoration: none; }
a:link:hover { text-decoration: underline; }
hr {
color:#cccccc;
background: #00007f;
height: 1px;
}
blockquote { margin-left: 3em; }
ul { list-style-type: disc; }
p.name {
font-family: "Andale Mono", monospace;
padding-top: 1em;
}
pre {
background-color: rgb(245, 245, 245);
border: 1px solid #C0C0C0; /* silver */
padding: 10px;
margin: 10px 0 10px 0;
overflow: auto;
font-family: "Andale Mono", monospace;
}
pre.example {
font-size: .85em;
}
table.index { border: 1px #00007f; }
table.index td { text-align: left; vertical-align: top; }
#container {
margin-left: 1em;
margin-right: 1em;
background-color: #f0f0f0;
}
#product {
text-align: center;
border-bottom: 1px solid #cccccc;
background-color: #ffffff;
}
#product big {
font-size: 2em;
}
#main {
background-color: #f0f0f0;
border-left: 2px solid #cccccc;
}
#navigation {
float: left;
width: 14em;
vertical-align: top;
background-color: #f0f0f0;
overflow: visible;
}
#navigation h2 {
background-color:#e7e7e7;
font-size:1.1em;
color:#000000;
text-align: left;
padding:0.2em;
border-top:1px solid #dddddd;
border-bottom:1px solid #dddddd;
}
#navigation ul
{
font-size:1em;
list-style-type: none;
margin: 1px 1px 10px 1px;
}
#navigation li {
text-indent: -1em;
display: block;
margin: 3px 0px 0px 22px;
}
#navigation li li a {
margin: 0px 3px 0px -1em;
}
#content {
margin-left: 14em;
padding: 1em;
width: 700px;
border-left: 2px solid #cccccc;
border-right: 2px solid #cccccc;
background-color: #ffffff;
}
#about {
clear: both;
padding: 5px;
border-top: 2px solid #cccccc;
background-color: #ffffff;
}
@media print {
body {
font: 12pt "Times New Roman", "TimeNR", Times, serif;
}
a { font-weight: bold; color: #004080; text-decoration: underline; }
#main {
background-color: #ffffff;
border-left: 0px;
}
#container {
margin-left: 2%;
margin-right: 2%;
background-color: #ffffff;
}
#content {
padding: 1em;
background-color: #ffffff;
}
#navigation {
display: none;
}
pre.example {
font-family: "Andale Mono", monospace;
font-size: 10pt;
page-break-inside: avoid;
}
}
table.module_list {
border-width: 1px;
border-style: solid;
border-color: #cccccc;
border-collapse: collapse;
}
table.module_list td {
border-width: 1px;
padding: 3px;
border-style: solid;
border-color: #cccccc;
}
table.module_list td.name { background-color: #f0f0f0; min-width: 200px; }
table.module_list td.summary { width: 100%; }
table.function_list {
border-width: 1px;
border-style: solid;
border-color: #cccccc;
border-collapse: collapse;
}
table.function_list td {
border-width: 1px;
padding: 3px;
border-style: solid;
border-color: #cccccc;
}
table.function_list td.name { background-color: #f0f0f0; min-width: 200px; }
table.function_list td.summary { width: 100%; }
ul.nowrap {
overflow:auto;
white-space:nowrap;
}
dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;}
dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;}
dl.table h3, dl.function h3 {font-size: .95em;}
/* stop sublists from having initial vertical space */
ul ul { margin-top: 0px; }
ol ul { margin-top: 0px; }
ol ol { margin-top: 0px; }
ul ol { margin-top: 0px; }
/* make the target distinct; helps when we're navigating to a function */
a:target + * {
background-color: #FF9;
}
/* styles for prettification of source */
pre .comment { color: #558817; }
pre .constant { color: #a8660d; }
pre .escape { color: #844631; }
pre .keyword { color: #aa5050; font-weight: bold; }
pre .library { color: #0e7c6b; }
pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; }
pre .string { color: #8080ff; }
pre .number { color: #f8660d; }
pre .operator { color: #2239a8; font-weight: bold; }
pre .preprocessor, pre .prepro { color: #a33243; }
pre .global { color: #800080; }
pre .user-keyword { color: #800080; }
pre .prompt { color: #558817; }
pre .url { color: #272fc2; text-decoration: underline; }

View file

@ -0,0 +1,267 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head>
<title>lyaml 6.2.8 Reference</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo"></div>
<div id="product_name"><big><b></b></big></div>
<div id="product_description"></div>
</div> <!-- id="product" -->
<div id="main">
<!-- Menu -->
<div id="navigation">
<br/>
<h1>lyaml 6.2.8</h1>
<ul>
<li><a href="../index.html">Index</a></li>
</ul>
<h2>Contents</h2>
<ul>
<li><a href="#Functions">Functions</a></li>
</ul>
<h2>Modules</h2>
<ul class="nowrap">
<li><a href="../modules/lyaml.html">lyaml</a></li>
<li><strong>lyaml.explicit</strong></li>
<li><a href="../modules/lyaml.functional.html">lyaml.functional</a></li>
<li><a href="../modules/lyaml.implicit.html">lyaml.implicit</a></li>
</ul>
</div>
<div id="content">
<h1>Module <code>lyaml.explicit</code></h1>
<p>
</p>
<p>
</p>
<h2><a href="#Functions">Functions</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#bool">bool (value)</a></td>
<td class="summary">Parse the value following an explicit <code>!!bool</code> tag.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#float">float (value)</a></td>
<td class="summary">Parse the value following an explicit <code>!!float</code> tag.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#int">int (value)</a></td>
<td class="summary">Parse the value following an explicit <code>!!int</code> tag.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#null">null ()</a></td>
<td class="summary">Parse an explicit <code>!!null</code> tag.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#str">str (value)</a></td>
<td class="summary">Parse the value following an explicit <code>!!str</code> tag.</td>
</tr>
</table>
<br/>
<br/>
<h2 class="section-header "><a name="Functions"></a>Functions</h2>
<dl class="function">
<dt>
<a name = "bool"></a>
<strong>bool (value)</strong>
</dt>
<dd>
Parse the value following an explicit <code>!!bool</code> tag.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../modules/lyaml.explicit.html#bool">bool</a></span>
boolean equivalent, if a valid value was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_bool = explicit.bool(tagarg)</pre>
</ul>
</dd>
<dt>
<a name = "float"></a>
<strong>float (value)</strong>
</dt>
<dd>
Parse the value following an explicit <code>!!float</code> tag.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">number</span></span>
float equivalent, if a valid value was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_float = explicit.float(tagarg)</pre>
</ul>
</dd>
<dt>
<a name = "int"></a>
<strong>int (value)</strong>
</dt>
<dd>
Parse the value following an explicit <code>!!int</code> tag.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../modules/lyaml.explicit.html#int">int</a></span>
integer equivalent, if a valid value was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_int = explicit.int(tagarg)</pre>
</ul>
</dd>
<dt>
<a name = "null"></a>
<strong>null ()</strong>
</dt>
<dd>
Parse an explicit <code>!!null</code> tag.
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">lyaml.null</span></span>
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">null = explicit.null(tagarg)</pre>
</ul>
</dd>
<dt>
<a name = "str"></a>
<strong>str (value)</strong>
</dt>
<dd>
Parse the value following an explicit <code>!!str</code> tag.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
<em>value</em> which was a string already
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">tagarg = explicit.str(tagarg)</pre>
</ul>
</dd>
</dl>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-22 17:12:03 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View file

@ -0,0 +1,236 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head>
<title>lyaml 6.2.8 Reference</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo"></div>
<div id="product_name"><big><b></b></big></div>
<div id="product_description"></div>
</div> <!-- id="product" -->
<div id="main">
<!-- Menu -->
<div id="navigation">
<br/>
<h1>lyaml 6.2.8</h1>
<ul>
<li><a href="../index.html">Index</a></li>
</ul>
<h2>Contents</h2>
<ul>
<li><a href="#Functions">Functions</a></li>
<li><a href="#Tables">Tables</a></li>
</ul>
<h2>Modules</h2>
<ul class="nowrap">
<li><a href="../modules/lyaml.html">lyaml</a></li>
<li><a href="../modules/lyaml.explicit.html">lyaml.explicit</a></li>
<li><strong>lyaml.functional</strong></li>
<li><a href="../modules/lyaml.implicit.html">lyaml.implicit</a></li>
</ul>
</div>
<div id="content">
<h1>Module <code>lyaml.functional</code></h1>
<p>
</p>
<p>
</p>
<h2><a href="#Functions">Functions</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#isnull">isnull (x)</a></td>
<td class="summary"><code>lyaml.null</code> predicate.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#iscallable">iscallable (x)</a></td>
<td class="summary">Callable predicate.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#anyof">anyof (fns)</a></td>
<td class="summary">Compose a function to try each callable with supplied args.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#id">id (...)</a></td>
<td class="summary">Return arguments unchanged.</td>
</tr>
</table>
<h2><a href="#Tables">Tables</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#NULL">NULL</a></td>
<td class="summary"><code>lyaml.null</code> value.</td>
</tr>
</table>
<br/>
<br/>
<h2 class="section-header "><a name="Functions"></a>Functions</h2>
<dl class="function">
<dt>
<a name = "isnull"></a>
<strong>isnull (x)</strong>
</dt>
<dd>
<code>lyaml.null</code> predicate.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">x</span>
operand
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">bool</span></span>
<code>true</code> if <em>x</em> is <code>lyaml.null</code>.
</ol>
</dd>
<dt>
<a name = "iscallable"></a>
<strong>iscallable (x)</strong>
</dt>
<dd>
Callable predicate.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">x</span>
operand
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">bool</span></span>
<code>true</code> if <em>x</em> is a function has a __call metamethod
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">r = iscallable(x) <span class="keyword">and</span> x(...)</pre>
</ul>
</dd>
<dt>
<a name = "anyof"></a>
<strong>anyof (fns)</strong>
</dt>
<dd>
Compose a function to try each callable with supplied args.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">fns</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span>
list of functions to try
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">function</span></span>
<p>a new function to call <em>...</em> functions, stopping</p>
<pre><code>and returning the first non-nil result, if any
</code></pre>
</ol>
</dd>
<dt>
<a name = "id"></a>
<strong>id (...)</strong>
</dt>
<dd>
Return arguments unchanged.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">...</span>
arguments
</li>
</ul>
<h3>Returns:</h3>
<ol>
<em>...</em>
</ol>
</dd>
</dl>
<h2 class="section-header "><a name="Tables"></a>Tables</h2>
<dl class="function">
<dt>
<a name = "NULL"></a>
<strong>NULL</strong>
</dt>
<dd>
<code>lyaml.null</code> value.
</dd>
</dl>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-22 17:12:03 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

224
doc/modules/lyaml.html Normal file
View file

@ -0,0 +1,224 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head>
<title>lyaml 6.2.8 Reference</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo"></div>
<div id="product_name"><big><b></b></big></div>
<div id="product_description"></div>
</div> <!-- id="product" -->
<div id="main">
<!-- Menu -->
<div id="navigation">
<br/>
<h1>lyaml 6.2.8</h1>
<ul>
<li><a href="../index.html">Index</a></li>
</ul>
<h2>Contents</h2>
<ul>
<li><a href="#Functions">Functions</a></li>
<li><a href="#Tables">Tables</a></li>
</ul>
<h2>Modules</h2>
<ul class="nowrap">
<li><strong>lyaml</strong></li>
<li><a href="../modules/lyaml.explicit.html">lyaml.explicit</a></li>
<li><a href="../modules/lyaml.functional.html">lyaml.functional</a></li>
<li><a href="../modules/lyaml.implicit.html">lyaml.implicit</a></li>
</ul>
</div>
<div id="content">
<h1>Module <code>lyaml</code></h1>
<p>
</p>
<p>
</p>
<h2><a href="#Functions">Functions</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#dump">dump (documents[, opts])</a></td>
<td class="summary">Dump a list of Lua tables to an equivalent YAML stream.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#load">load (s[, opts])</a></td>
<td class="summary">Load a YAML stream into a Lua table.</td>
</tr>
</table>
<h2><a href="#Tables">Tables</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#dumper_opts">dumper_opts</a></td>
<td class="summary">Dump options table.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#loader_opts">loader_opts</a></td>
<td class="summary">Load options table.</td>
</tr>
</table>
<br/>
<br/>
<h2 class="section-header "><a name="Functions"></a>Functions</h2>
<dl class="function">
<dt>
<a name = "dump"></a>
<strong>dump (documents[, opts])</strong>
</dt>
<dd>
Dump a list of Lua tables to an equivalent YAML stream.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">documents</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span>
a sequence of Lua tables.
</li>
<li><span class="parameter">opts</span>
<span class="types"><span class="type">dumper_opts</span></span>
initialisation options
(<em>optional</em>)
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
equivalest YAML stream
</ol>
</dd>
<dt>
<a name = "load"></a>
<strong>load (s[, opts])</strong>
</dt>
<dd>
Load a YAML stream into a Lua table.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">s</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
YAML stream
</li>
<li><span class="parameter">opts</span>
<span class="types"><span class="type">loader_opts</span></span>
initialisation options
(<em>optional</em>)
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span>
Lua table equivalent of stream <em>s</em>
</ol>
</dd>
</dl>
<h2 class="section-header "><a name="Tables"></a>Tables</h2>
<dl class="function">
<dt>
<a name = "dumper_opts"></a>
<strong>dumper_opts</strong>
</dt>
<dd>
Dump options table.
<h3>Fields:</h3>
<ul>
<li><span class="parameter">anchors</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span>
map initial anchor names to values
</li>
<li><span class="parameter">implicit_scalar</span>
<span class="types"><span class="type">function</span></span>
parse implicit scalar values
</li>
</ul>
</dd>
<dt>
<a name = "loader_opts"></a>
<strong>loader_opts</strong>
</dt>
<dd>
Load options table.
<h3>Fields:</h3>
<ul>
<li><span class="parameter">all</span>
<span class="types"><span class="type">boolean</span></span>
load all documents from the stream
</li>
<li><span class="parameter">explicit_scalar</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span>
map full tag-names to parser functions
</li>
<li><span class="parameter">implicit_scalar</span>
<span class="types"><span class="type">function</span></span>
parse implicit scalar values
</li>
</ul>
</dd>
</dl>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-22 17:12:03 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View file

@ -0,0 +1,533 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head>
<title>lyaml 6.2.8 Reference</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo"></div>
<div id="product_name"><big><b></b></big></div>
<div id="product_description"></div>
</div> <!-- id="product" -->
<div id="main">
<!-- Menu -->
<div id="navigation">
<br/>
<h1>lyaml 6.2.8</h1>
<ul>
<li><a href="../index.html">Index</a></li>
</ul>
<h2>Contents</h2>
<ul>
<li><a href="#Functions">Functions</a></li>
</ul>
<h2>Modules</h2>
<ul class="nowrap">
<li><a href="../modules/lyaml.html">lyaml</a></li>
<li><a href="../modules/lyaml.explicit.html">lyaml.explicit</a></li>
<li><a href="../modules/lyaml.functional.html">lyaml.functional</a></li>
<li><strong>lyaml.implicit</strong></li>
</ul>
</div>
<div id="content">
<h1>Module <code>lyaml.implicit</code></h1>
<p>
</p>
<p>
</p>
<h2><a href="#Functions">Functions</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#null">null (value)</a></td>
<td class="summary">Parse a null token to a null value.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#bool">bool (value)</a></td>
<td class="summary">Parse a boolean token to the equivalent value.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#binary">binary (value)</a></td>
<td class="summary">Parse a binary token, such as '0b1010_0111_0100_1010_1110'.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#octal">octal (value)</a></td>
<td class="summary">Parse an octal token, such as '012345'.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#decimal">decimal (value)</a></td>
<td class="summary">Parse a decimal token, such as '0' or '12345'.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#hexadecimal">hexadecimal (value)</a></td>
<td class="summary">Parse a hexadecimal token, such as '0xdeadbeef'.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#sexagesimal">sexagesimal (value)</a></td>
<td class="summary">Parse a sexagesimal token, such as '190:20:30'.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#nan">nan (value)</a></td>
<td class="summary">Parse a <code>nan</code> token.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#inf">inf (value)</a></td>
<td class="summary">Parse a signed <code>inf</code> token.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#float">float (value)</a></td>
<td class="summary">Parse a floating point number token, such as '1e-3' or '-0.12'.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#sexfloat">sexfloat (value)</a></td>
<td class="summary">Parse a sexagesimal float, such as '190:20:30.15'.</td>
</tr>
</table>
<br/>
<br/>
<h2 class="section-header "><a name="Functions"></a>Functions</h2>
<dl class="function">
<dt>
<a name = "null"></a>
<strong>null (value)</strong>
</dt>
<dd>
Parse a null token to a null value.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
lyaml.null, for an empty string or literal ~
</ol>
<h3>Or</h3>
<ol>
nil otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_null = implicit.null(token)</pre>
</ul>
</dd>
<dt>
<a name = "bool"></a>
<strong>bool (value)</strong>
</dt>
<dd>
Parse a boolean token to the equivalent value.
Treats capilalized, lower and upper-cased variants of true/false,
yes/no or on/off tokens as boolean <code>true</code> and <code>false</code> values.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../modules/lyaml.implicit.html#bool">bool</a></span>
if a valid boolean token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_bool = implicit.bool(token)</pre>
</ul>
</dd>
<dt>
<a name = "binary"></a>
<strong>binary (value)</strong>
</dt>
<dd>
Parse a binary token, such as '0b1010_0111_0100_1010_1110'.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">int</span></span>
integer equivalent, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_int = implicit.binary(value)</pre>
</ul>
</dd>
<dt>
<a name = "octal"></a>
<strong>octal (value)</strong>
</dt>
<dd>
Parse an octal token, such as '012345'.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">int</span></span>
integer equivalent, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_int = implicit.octal(value)</pre>
</ul>
</dd>
<dt>
<a name = "decimal"></a>
<strong>decimal (value)</strong>
</dt>
<dd>
Parse a decimal token, such as '0' or '12345'.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">int</span></span>
integer equivalent, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_int = implicit.decimal(value)</pre>
</ul>
</dd>
<dt>
<a name = "hexadecimal"></a>
<strong>hexadecimal (value)</strong>
</dt>
<dd>
Parse a hexadecimal token, such as '0xdeadbeef'.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">int</span></span>
integer equivalent, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_int = implicit.hexadecimal(value)</pre>
</ul>
</dd>
<dt>
<a name = "sexagesimal"></a>
<strong>sexagesimal (value)</strong>
</dt>
<dd>
Parse a sexagesimal token, such as '190:20:30'.
Useful for times and angles.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">int</span></span>
integer equivalent, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_int = implicit.sexagesimal(value)</pre>
</ul>
</dd>
<dt>
<a name = "nan"></a>
<strong>nan (value)</strong>
</dt>
<dd>
Parse a <code>nan</code> token.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../modules/lyaml.implicit.html#nan">nan</a></span>
not-a-number, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_nan = implicit.nan(value)</pre>
</ul>
</dd>
<dt>
<a name = "inf"></a>
<strong>inf (value)</strong>
</dt>
<dd>
Parse a signed <code>inf</code> token.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">number</span></span>
plus/minus-infinity, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_inf = implicit.inf(value)</pre>
</ul>
</dd>
<dt>
<a name = "float"></a>
<strong>float (value)</strong>
</dt>
<dd>
Parse a floating point number token, such as '1e-3' or '-0.12'.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">number</span></span>
float equivalent, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_float = implicit.float(value)</pre>
</ul>
</dd>
<dt>
<a name = "sexfloat"></a>
<strong>sexfloat (value)</strong>
</dt>
<dd>
Parse a sexagesimal float, such as '190:20:30.15'.
Useful for times and angles.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
token
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">number</span></span>
float equivalent, if a valid token was recognized
</ol>
<h3>Or</h3>
<ol>
<span class="types"><span class="type">nil</span></span>
otherwise, nil
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">maybe_float = implicit.sexfloat(value)</pre>
</ul>
</dd>
</dl>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-22 17:12:03 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

459
ext/yaml/emitter.c Normal file
View file

@ -0,0 +1,459 @@
/*
* emitter.c, LibYAML emitter binding for Lua
* Written by Gary V. Vaughan, 2013
*
* Copyright (C) 2013-2022 Gary V. Vaughan
*
* 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.
*/
#include <assert.h>
#include "lyaml.h"
typedef struct {
yaml_emitter_t emitter;
/* output accumulator */
lua_State *outputL;
luaL_Buffer yamlbuff;
/* error handling */
lua_State *errL;
luaL_Buffer errbuff;
int error;
} lyaml_emitter;
/* Emit a STREAM_START event. */
static int
emit_STREAM_START (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_encoding_t yaml_encoding;
const char *encoding = NULL;
RAWGET_STRDUP (encoding); lua_pop (L, 1);
#define MENTRY(_s) (STREQ (encoding, #_s)) { yaml_encoding = YAML_##_s##_ENCODING; }
if (encoding == NULL) { yaml_encoding = YAML_ANY_ENCODING; } else
if MENTRY( UTF8 ) else
if MENTRY( UTF16LE ) else
if MENTRY( UTF16BE ) else
{
emitter->error++;
luaL_addsize (&emitter->errbuff,
sprintf (luaL_prepbuffer (&emitter->errbuff),
"invalid stream encoding '%s'", encoding));
}
#undef MENTRY
if (encoding) free ((void *) encoding);
if (emitter->error != 0)
return 0;
yaml_stream_start_event_initialize (&event, yaml_encoding);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit a STREAM_END event. */
static int
emit_STREAM_END (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_stream_end_event_initialize (&event);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit a DOCUMENT_START event. */
static int
emit_DOCUMENT_START (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_version_directive_t version_directive, *Pversion_directive = NULL;
yaml_tag_directive_t *tag_directives_start = NULL, *tag_directives_end = NULL;
int implicit = 0;
RAWGET_PUSHTABLE ("version_directive");
if (lua_type (L, -1) == LUA_TTABLE)
{
RAWGETS_INTEGER (version_directive.major, "major");
ERROR_IFNIL ("version_directive missing key 'major'");
if (emitter->error == 0)
{
RAWGETS_INTEGER (version_directive.minor, "minor");
ERROR_IFNIL ("version_directive missing key 'minor'");
}
Pversion_directive = &version_directive;
}
lua_pop (L, 1); /* pop version_directive rawget */
RAWGET_PUSHTABLE ("tag_directives");
if (lua_type (L, -1) == LUA_TTABLE)
{
size_t bytes = lua_objlen (L, -1) * sizeof (yaml_tag_directive_t);
tag_directives_start = (yaml_tag_directive_t *) malloc (bytes);
tag_directives_end = tag_directives_start;
lua_pushnil (L); /* first key */
while (lua_next (L, -2) != 0)
{
RAWGETS_STRDUP (tag_directives_end->handle, "handle");
ERROR_IFNIL ("tag_directives item missing key 'handle'");
lua_pop (L, 1); /* pop handle */
RAWGETS_STRDUP (tag_directives_end->prefix, "prefix");
ERROR_IFNIL ("tag_directives item missing key 'prefix'");
lua_pop (L, 1); /* pop prefix */
tag_directives_end += 1;
/* pop tag_directives list elewent, leave key for next iteration */
lua_pop (L, 1);
}
}
lua_pop (L, 1); /* pop lua_rawget "tag_directives" result */
RAWGET_BOOLEAN (implicit); lua_pop (L, 1);
if (emitter->error != 0)
return 0;
yaml_document_start_event_initialize (&event, Pversion_directive,
tag_directives_start, tag_directives_end, implicit);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit a DOCUMENT_END event. */
static int
emit_DOCUMENT_END (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
int implicit = 0;
RAWGET_BOOLEAN (implicit);
yaml_document_end_event_initialize (&event, implicit);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit a MAPPING_START event. */
static int
emit_MAPPING_START (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_mapping_style_t yaml_style;
yaml_char_t *anchor = NULL, *tag = NULL;
int implicit = 1;
const char *style = NULL;
RAWGET_STRDUP (style); lua_pop (L, 1);
#define MENTRY(_s) (STREQ (style, #_s)) { yaml_style = YAML_##_s##_MAPPING_STYLE; }
if (style == NULL) { yaml_style = YAML_ANY_MAPPING_STYLE; } else
if MENTRY( BLOCK ) else
if MENTRY( FLOW ) else
{
emitter->error++;
luaL_addsize (&emitter->errbuff,
sprintf (luaL_prepbuffer (&emitter->errbuff),
"invalid mapping style '%s'", style));
}
#undef MENTRY
if (style) free ((void *) style);
RAWGET_YAML_CHARP (anchor); lua_pop (L, 1);
RAWGET_YAML_CHARP (tag); lua_pop (L, 1);
RAWGET_BOOLEAN (implicit); lua_pop (L, 1);
yaml_mapping_start_event_initialize (&event, anchor, tag, implicit, yaml_style);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit a MAPPING_END event. */
static int
emit_MAPPING_END (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_mapping_end_event_initialize (&event);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit a SEQUENCE_START event. */
static int
emit_SEQUENCE_START (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_sequence_style_t yaml_style;
yaml_char_t *anchor = NULL, *tag = NULL;
int implicit = 1;
const char *style = NULL;
RAWGET_STRDUP (style); lua_pop (L, 1);
#define MENTRY(_s) (STREQ (style, #_s)) { yaml_style = YAML_##_s##_SEQUENCE_STYLE; }
if (style == NULL) { yaml_style = YAML_ANY_SEQUENCE_STYLE; } else
if MENTRY( BLOCK ) else
if MENTRY( FLOW ) else
{
emitter->error++;
luaL_addsize (&emitter->errbuff,
sprintf (luaL_prepbuffer (&emitter->errbuff),
"invalid sequence style '%s'", style));
}
#undef MENTRY
if (style) free ((void *) style);
RAWGET_YAML_CHARP (anchor); lua_pop (L, 1);
RAWGET_YAML_CHARP (tag); lua_pop (L, 1);
RAWGET_BOOLEAN (implicit); lua_pop (L, 1);
yaml_sequence_start_event_initialize (&event, anchor, tag, implicit, yaml_style);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit a SEQUENCE_END event. */
static int
emit_SEQUENCE_END (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_sequence_end_event_initialize (&event);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit a SCALAR event. */
static int
emit_SCALAR (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_scalar_style_t yaml_style;
yaml_char_t *anchor = NULL, *tag = NULL, *value;
int length = 0, plain_implicit = 1, quoted_implicit = 1;
const char *style = NULL;
RAWGET_STRDUP (style); lua_pop (L, 1);
#define MENTRY(_s) (STREQ (style, #_s)) { yaml_style = YAML_##_s##_SCALAR_STYLE; }
if (style == NULL) { yaml_style = YAML_ANY_SCALAR_STYLE; } else
if MENTRY( PLAIN ) else
if MENTRY( SINGLE_QUOTED ) else
if MENTRY( DOUBLE_QUOTED ) else
if MENTRY( LITERAL ) else
if MENTRY( FOLDED ) else
{
emitter->error++;
luaL_addsize (&emitter->errbuff,
sprintf (luaL_prepbuffer (&emitter->errbuff),
"invalid scalar style '%s'", style));
}
#undef MENTRY
if (style) free ((void *) style);
RAWGET_YAML_CHARP (anchor); lua_pop (L, 1);
RAWGET_YAML_CHARP (tag); lua_pop (L, 1);
RAWGET_YAML_CHARP (value); length = lua_objlen (L, -1); lua_pop (L, 1);
RAWGET_BOOLEAN (plain_implicit);
RAWGET_BOOLEAN (quoted_implicit);
yaml_scalar_event_initialize (&event, anchor, tag, value, length,
plain_implicit, quoted_implicit, yaml_style);
return yaml_emitter_emit (&emitter->emitter, &event);
}
/* Emit an ALIAS event. */
static int
emit_ALIAS (lua_State *L, lyaml_emitter *emitter)
{
yaml_event_t event;
yaml_char_t *anchor;
RAWGET_YAML_CHARP (anchor);
yaml_alias_event_initialize (&event, anchor);
return yaml_emitter_emit (&emitter->emitter, &event);
}
static int
emit (lua_State *L)
{
lyaml_emitter *emitter;
int yaml_ok = 0;
int finalize = 0;
luaL_argcheck (L, lua_istable (L, 1), 1, "expected table");
emitter = (lyaml_emitter *) lua_touserdata (L, lua_upvalueindex (1));
{
const char *type;
RAWGET_STRDUP (type); lua_pop (L, 1);
if (type == NULL)
{
emitter->error++;
luaL_addstring (&emitter->errbuff, "no type field in event table");
}
#define MENTRY(_s) (STREQ (type, #_s)) { yaml_ok = emit_##_s (L, emitter); }
/* Minimize comparisons by putting more common types earlier. */
else if MENTRY( SCALAR )
else if MENTRY( MAPPING_START )
else if MENTRY( MAPPING_END )
else if MENTRY( SEQUENCE_START )
else if MENTRY( SEQUENCE_END )
else if MENTRY( DOCUMENT_START )
else if MENTRY( DOCUMENT_END )
else if MENTRY( STREAM_START )
else if MENTRY( STREAM_END )
else if MENTRY( ALIAS )
#undef MENTRY
else
{
emitter->error++;
luaL_addsize (&emitter->errbuff,
sprintf (luaL_prepbuffer (&emitter->errbuff),
"invalid event type '%s'", type));
}
/* If the stream has finished, finalize the YAML output. */
if (type && STREQ (type, "STREAM_END"))
finalize = 1;
if (type) free ((void *) type);
}
/* Copy any yaml_emitter_t errors into the error buffer. */
if (!emitter->error && !yaml_ok)
{
if (emitter->emitter.problem)
luaL_addstring (&emitter->errbuff, emitter->emitter.problem);
else
luaL_addstring (&emitter->errbuff, "LibYAML call failed");
emitter->error++;
}
/* Report errors back to the caller as `false, "error message"`. */
if (emitter->error != 0)
{
assert (emitter->error == 1); /* bail on uncaught additional errors */
lua_pushboolean (L, 0);
luaL_pushresult (&emitter->errbuff);
lua_xmove (emitter->errL, L, 1);
return 2;
}
/* Return `true, "YAML string"` after accepting a STREAM_END event. */
if (finalize)
{
lua_pushboolean (L, 1);
luaL_pushresult (&emitter->yamlbuff);
lua_xmove (emitter->outputL, L, 1);
return 2;
}
/* Otherwise, just report success to the caller as `true`. */
lua_pushboolean (L, 1);
return 1;
}
static int
append_output (void *arg, unsigned char *buff, size_t len)
{
lyaml_emitter *emitter = (lyaml_emitter *) arg;
luaL_addlstring (&emitter->yamlbuff, (char *) buff, len);
return 1;
}
static int
emitter_gc (lua_State *L)
{
lyaml_emitter *emitter = (lyaml_emitter *) lua_touserdata (L, 1);
if (emitter)
yaml_emitter_delete (&emitter->emitter);
return 0;
}
int
Pemitter (lua_State *L)
{
lyaml_emitter *emitter;
lua_newtable (L); /* object table */
/* Create a user datum to store the emitter. */
emitter = (lyaml_emitter *) lua_newuserdata (L, sizeof (*emitter));
emitter->error = 0;
/* Initialize the emitter. */
if (!yaml_emitter_initialize (&emitter->emitter))
{
if (!emitter->emitter.problem)
emitter->emitter.problem = "cannot initialize emitter";
return luaL_error (L, "%s", emitter->emitter.problem);
}
yaml_emitter_set_unicode (&emitter->emitter, 1);
yaml_emitter_set_width (&emitter->emitter, 2);
yaml_emitter_set_output (&emitter->emitter, &append_output, emitter);
/* Set it's metatable, and ensure it is garbage collected properly. */
luaL_newmetatable (L, "lyaml.emitter");
lua_pushcfunction (L, emitter_gc);
lua_setfield (L, -2, "__gc");
lua_setmetatable (L, -2);
/* Set the emit method of object as a closure over the user datum, and
return the whole object. */
lua_pushcclosure (L, emit, 1);
lua_setfield (L, -2, "emit");
/* Set up a separate thread to collect error messages; save the thread
in the returned table so that it's not garbage collected when the
function call stack for Pemitter is cleaned up. */
emitter->errL = lua_newthread (L);
luaL_buffinit (emitter->errL, &emitter->errbuff);
lua_setfield (L, -2, "errthread");
/* Create a thread for the YAML buffer. */
emitter->outputL = lua_newthread (L);
luaL_buffinit (emitter->outputL, &emitter->yamlbuff);
lua_setfield (L, -2, "outputthread");
return 1;
}

161
ext/yaml/lyaml.h Normal file
View file

@ -0,0 +1,161 @@
/*
* lyaml.h, libyaml parser binding for Lua
* Written by Gary V. Vaughan, 2013
*
* Copyright (C) 2013-2022 Gary V. Vaughan
*
* 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.
*/
#ifndef LYAML_H
#define LYAML_H 1
#include <yaml.h>
#include <lua.h>
#include <lauxlib.h>
#include "lyaml.h"
#if LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 || LUA_VERSION_NUM == 504
# define lua_objlen lua_rawlen
# define lua_strlen lua_rawlen
# define luaL_openlib(L,n,l,nup) luaL_setfuncs((L),(l),(nup))
# define luaL_register(L,n,l) (luaL_newlib(L,l))
#endif
#ifndef STREQ
#define STREQ !strcmp
#endif
#ifndef STRNEQ
#define STRNEQ strcmp
#endif
/* NOTE: Make sure L is in scope before using these macros.
lua_pushyamlstr casts away the impedance mismatch between Lua's
signed char APIs and libYAML's unsigned char APIs. */
#define lua_pushyamlstr(_s) lua_pushstring (L, (char *)(_s))
#define RAWSET_BOOLEAN(_k, _v) \
lua_pushyamlstr (_k); \
lua_pushboolean (L, (_v) != 0); \
lua_rawset (L, -3)
#define RAWSET_INTEGER(_k, _v) \
lua_pushyamlstr (_k); \
lua_pushinteger (L, (_v)); \
lua_rawset (L, -3)
#define RAWSET_STRING(_k, _v) \
lua_pushyamlstr (_k); \
lua_pushyamlstr (_v); \
lua_rawset (L, -3)
#define RAWSET_EVENTF(_k) \
lua_pushstring (L, #_k); \
lua_pushyamlstr (EVENTF(_k)); \
lua_rawset (L, -3)
/* NOTE: Make sure L is in scope before using these macros.
The table value at _k is not popped from the stack for strings
or tables, so that we can check for an empty table entry with
lua_isnil (L, -1), or get the length of a string with
lua_objlen (L, -1) before popping. */
#define RAWGET_BOOLEAN(_k) \
lua_pushstring (L, #_k); \
lua_rawget (L, -2); \
if (!lua_isnil (L, -1)) { \
_k = lua_toboolean (L, -1); \
} \
lua_pop (L, 1)
#define RAWGET_INTEGER(_k) \
lua_pushstring (L, #_k); \
lua_rawget (L, -2); \
if (!lua_isnil (L, -1)) { \
_k = lua_tointeger (L, -1); \
} \
lua_pop (L, 1)
#define RAWGET_STRING(_k) \
lua_pushstring (L, #_k); \
lua_rawget (L, -2); \
if (!lua_isnil (L, -1)) { \
_k = lua_tostring (L, -1); \
} else { _k = NULL; }
#define RAWGET_STRDUP(_k) \
lua_pushstring (L, #_k); \
lua_rawget (L, -2); \
if (!lua_isnil (L, -1)) { \
_k = strdup (lua_tostring (L, -1)); \
} else { _k = NULL; }
#define RAWGET_YAML_CHARP(_k) \
lua_pushstring (L, #_k); \
lua_rawget (L, -2); \
if (!lua_isnil (L, -1)) { \
_k = (yaml_char_t *) lua_tostring (L, -1); \
} else { _k = NULL; }
#define RAWGETS_INTEGER(_v, _s) \
lua_pushstring (L, _s); \
lua_rawget (L, -2); \
if (!lua_isnil (L, -1)) { \
_v = lua_tointeger (L, -1); \
} \
lua_pop (L, 1)
#define RAWGETS_STRDUP( _v, _s) \
lua_pushstring (L, _s); \
lua_rawget (L, -2); \
if (!lua_isnil (L, -1)) { \
_v = (yaml_char_t *) strdup (lua_tostring (L, -1)); \
} else { _v = NULL; }
#define RAWGET_PUSHTABLE(_k) \
lua_pushstring (L, _k); \
lua_rawget (L, -2); \
if ((lua_type (L, -1) != LUA_TTABLE) && !lua_isnil (L, -1)) { \
lua_pop (L, 1); \
return luaL_error (L, "%s must be a table", _k); \
}
#define ERROR_IFNIL(_err) \
if (lua_isnil (L, -1)) { \
emitter->error++; \
luaL_addstring (&emitter->errbuff, _err); \
}
/* from emitter.c */
extern int Pemitter (lua_State *L);
/* from parser.c */
extern void parser_init (lua_State *L);
extern int Pparser (lua_State *L);
/* from scanner.c */
extern void scanner_init (lua_State *L);
extern int Pscanner (lua_State *L);
#endif

410
ext/yaml/parser.c Normal file
View file

@ -0,0 +1,410 @@
/*
* parser.c, libyaml parser binding for Lua
* Written by Gary V. Vaughan, 2013
*
* Copyright (C) 2013-2022 Gary V. Vaughan
*
* 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.
*/
#include "lyaml.h"
typedef struct {
lua_State *L;
yaml_parser_t parser;
yaml_event_t event;
char validevent;
int document_count;
} lyaml_parser;
static void
parser_delete_event (lyaml_parser *parser)
{
if (parser->validevent)
{
yaml_event_delete (&parser->event);
parser->validevent = 0;
}
}
/* With the event result table on the top of the stack, insert
a mark entry. */
static void
parser_set_mark (lua_State *L, const char *k, yaml_mark_t mark)
{
lua_pushstring (L, k);
lua_createtable (L, 0, 3);
#define MENTRY(_s) RAWSET_INTEGER(#_s, mark._s)
MENTRY( index );
MENTRY( line );
MENTRY( column );
#undef MENTRY
lua_rawset (L, -3);
}
/* Push a new event table, pre-populated with shared elements. */
static void
parser_push_eventtable (lyaml_parser *parser, const char *v, int n)
{
lua_State *L = parser->L;
lua_createtable (L, 0, n + 3);
RAWSET_STRING ("type", v);
#define MENTRY(_s) parser_set_mark (L, #_s, parser->event._s)
MENTRY( start_mark );
MENTRY( end_mark );
#undef MENTRY
}
static void
parse_STREAM_START (lyaml_parser *parser)
{
#define EVENTF(_f) (parser->event.data.stream_start._f)
lua_State *L = parser->L;
const char *encoding;
switch (EVENTF (encoding))
{
#define MENTRY(_s) \
case YAML_##_s##_ENCODING: encoding = #_s; break
MENTRY( ANY );
MENTRY( UTF8 );
MENTRY( UTF16LE );
MENTRY( UTF16BE );
#undef MENTRY
default:
lua_pushfstring (L, "invalid encoding %d", EVENTF (encoding));
lua_error (L);
}
parser_push_eventtable (parser, "STREAM_START", 1);
RAWSET_STRING ("encoding", encoding);
#undef EVENTF
}
/* With the tag list on the top of the stack, append TAG. */
static void
parser_append_tag (lua_State *L, yaml_tag_directive_t tag)
{
lua_createtable (L, 0, 2);
#define MENTRY(_s) RAWSET_STRING(#_s, tag._s)
MENTRY( handle );
MENTRY( prefix );
#undef MENTRY
lua_rawseti (L, -2, lua_objlen (L, -2) + 1);
}
static void
parse_DOCUMENT_START (lyaml_parser *parser)
{
#define EVENTF(_f) (parser->event.data.document_start._f)
lua_State *L = parser->L;
/* increment document count */
parser->document_count++;
parser_push_eventtable (parser, "DOCUMENT_START", 1);
RAWSET_BOOLEAN ("implicit", EVENTF (implicit));
/* version_directive = { major = M, minor = N } */
if (EVENTF (version_directive))
{
lua_pushliteral (L, "version_directive");
lua_createtable (L, 0, 2);
#define MENTRY(_s) RAWSET_INTEGER(#_s, EVENTF (version_directive->_s))
MENTRY( major );
MENTRY( minor );
#undef MENTRY
lua_rawset (L, -3);
}
/* tag_directives = { {handle = H1, prefix = P1}, ... } */
if (EVENTF (tag_directives.start) &&
EVENTF (tag_directives.end)) {
yaml_tag_directive_t *cur;
lua_pushliteral (L, "tag_directives");
lua_newtable (L);
for (cur = EVENTF (tag_directives.start);
cur != EVENTF (tag_directives.end);
cur = cur + 1)
{
parser_append_tag (L, *cur);
}
lua_rawset (L, -3);
}
#undef EVENTF
}
static void
parse_DOCUMENT_END (lyaml_parser *parser)
{
#define EVENTF(_f) (parser->event.data.document_end._f)
lua_State *L = parser->L;
parser_push_eventtable (parser, "DOCUMENT_END", 1);
RAWSET_BOOLEAN ("implicit", EVENTF (implicit));
#undef EVENTF
}
static void
parse_ALIAS (lyaml_parser *parser)
{
#define EVENTF(_f) (parser->event.data.alias._f)
lua_State *L = parser->L;
parser_push_eventtable (parser, "ALIAS", 1);
RAWSET_EVENTF (anchor);
#undef EVENTF
}
static void
parse_SCALAR (lyaml_parser *parser)
{
#define EVENTF(_f) (parser->event.data.scalar._f)
lua_State *L = parser->L;
const char *style;
switch (EVENTF (style))
{
#define MENTRY(_s) \
case YAML_##_s##_SCALAR_STYLE: style = #_s; break
MENTRY( ANY );
MENTRY( PLAIN );
MENTRY( SINGLE_QUOTED );
MENTRY( DOUBLE_QUOTED );
MENTRY( LITERAL );
MENTRY( FOLDED );
#undef MENTRY
default:
lua_pushfstring (L, "invalid sequence style %d", EVENTF (style));
lua_error (L);
}
parser_push_eventtable (parser, "SCALAR", 6);
RAWSET_EVENTF (anchor);
RAWSET_EVENTF (tag);
RAWSET_EVENTF (value);
RAWSET_BOOLEAN ("plain_implicit", EVENTF (plain_implicit));
RAWSET_BOOLEAN ("quoted_implicit", EVENTF (quoted_implicit));
RAWSET_STRING ("style", style);
#undef EVENTF
}
static void
parse_SEQUENCE_START (lyaml_parser *parser)
{
#define EVENTF(_f) (parser->event.data.sequence_start._f)
lua_State *L = parser->L;
const char *style;
switch (EVENTF (style))
{
#define MENTRY(_s) \
case YAML_##_s##_SEQUENCE_STYLE: style = #_s; break
MENTRY( ANY );
MENTRY( BLOCK );
MENTRY( FLOW );
#undef MENTRY
default:
lua_pushfstring (L, "invalid sequence style %d", EVENTF (style));
lua_error (L);
}
parser_push_eventtable (parser, "SEQUENCE_START", 4);
RAWSET_EVENTF (anchor);
RAWSET_EVENTF (tag);
RAWSET_BOOLEAN ("implicit", EVENTF (implicit));
RAWSET_STRING ("style", style);
#undef EVENTF
}
static void
parse_MAPPING_START (lyaml_parser *parser)
{
#define EVENTF(_f) (parser->event.data.mapping_start._f)
lua_State *L = parser->L;
const char *style;
switch (EVENTF (style))
{
#define MENTRY(_s) \
case YAML_##_s##_MAPPING_STYLE: style = #_s; break
MENTRY( ANY );
MENTRY( BLOCK );
MENTRY( FLOW );
#undef MENTRY
default:
lua_pushfstring (L, "invalid mapping style %d", EVENTF (style));
lua_error (L);
}
parser_push_eventtable (parser, "MAPPING_START", 4);
RAWSET_EVENTF (anchor);
RAWSET_EVENTF (tag);
RAWSET_BOOLEAN ("implicit", EVENTF (implicit));
RAWSET_STRING ("style", style);
#undef EVENTF
}
static void
parser_generate_error_message (lyaml_parser *parser)
{
yaml_parser_t *P = &parser->parser;
char buf[256];
luaL_Buffer b;
luaL_buffinit (parser->L, &b);
luaL_addstring (&b, P->problem ? P->problem : "A problem");
snprintf (buf, sizeof (buf), " at document: %d", parser->document_count);
luaL_addstring (&b, buf);
if (P->problem_mark.line || P->problem_mark.column)
{
snprintf (buf, sizeof (buf), ", line: %lu, column: %lu",
(unsigned long) P->problem_mark.line + 1,
(unsigned long) P->problem_mark.column + 1);
luaL_addstring (&b, buf);
}
luaL_addstring (&b, "\n");
if (P->context)
{
snprintf (buf, sizeof (buf), "%s at line: %lu, column: %lu\n",
P->context,
(unsigned long) P->context_mark.line + 1,
(unsigned long) P->context_mark.column + 1);
luaL_addstring (&b, buf);
}
luaL_pushresult (&b);
}
static int
event_iter (lua_State *L)
{
lyaml_parser *parser = (lyaml_parser *)lua_touserdata(L, lua_upvalueindex(1));
char *str;
parser_delete_event (parser);
if (yaml_parser_parse (&parser->parser, &parser->event) != 1)
{
parser_generate_error_message (parser);
return lua_error (L);
}
parser->validevent = 1;
lua_newtable (L);
lua_pushliteral (L, "type");
switch (parser->event.type)
{
/* First the simple events, generated right here... */
#define MENTRY(_s) \
case YAML_##_s##_EVENT: parser_push_eventtable (parser, #_s, 0); break
MENTRY( STREAM_END );
MENTRY( SEQUENCE_END );
MENTRY( MAPPING_END );
#undef MENTRY
/* ...then the complex events, generated by a function call. */
#define MENTRY(_s) \
case YAML_##_s##_EVENT: parse_##_s (parser); break
MENTRY( STREAM_START );
MENTRY( DOCUMENT_START );
MENTRY( DOCUMENT_END );
MENTRY( ALIAS );
MENTRY( SCALAR );
MENTRY( SEQUENCE_START );
MENTRY( MAPPING_START );
#undef MENTRY
case YAML_NO_EVENT:
lua_pushnil (L);
break;
default:
lua_pushfstring (L, "invalid event %d", parser->event.type);
return lua_error (L);
}
return 1;
}
static int
parser_gc (lua_State *L)
{
lyaml_parser *parser = (lyaml_parser *) lua_touserdata (L, 1);
if (parser)
{
parser_delete_event (parser);
yaml_parser_delete (&parser->parser);
}
return 0;
}
void
parser_init (lua_State *L)
{
luaL_newmetatable(L, "lyaml.parser");
lua_pushcfunction(L, parser_gc);
lua_setfield(L, -2, "__gc");
}
int
Pparser (lua_State *L)
{
lyaml_parser *parser;
const unsigned char *str;
/* requires a single string type argument */
luaL_argcheck (L, lua_isstring (L, 1), 1, "must provide a string argument");
str = (const unsigned char *) lua_tostring (L, 1);
/* create a user datum to store the parser */
parser = (lyaml_parser *) lua_newuserdata (L, sizeof (*parser));
memset ((void *) parser, 0, sizeof (*parser));
parser->L = L;
/* set its metatable */
luaL_getmetatable (L, "lyaml.parser");
lua_setmetatable (L, -2);
/* try to initialize the parser */
if (yaml_parser_initialize (&parser->parser) == 0)
luaL_error (L, "cannot initialize parser for %s", str);
yaml_parser_set_input_string (&parser->parser, str, lua_strlen (L, 1));
/* create and return the iterator function, with the loader userdatum as
its sole upvalue */
lua_pushcclosure (L, event_iter, 1);
return 1;
}

340
ext/yaml/scanner.c Normal file
View file

@ -0,0 +1,340 @@
/*
* scanner.c, libyaml scanner binding for Lua
* Written by Gary V. Vaughan, 2013
*
* Copyright (C) 2013-2022 Gary V. Vaughan
*
* 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.
*/
#include "lyaml.h"
typedef struct {
lua_State *L;
yaml_parser_t parser;
yaml_token_t token;
char validtoken;
int document_count;
} lyaml_scanner;
static void
scanner_delete_token (lyaml_scanner *scanner)
{
if (scanner->validtoken)
{
yaml_token_delete (&scanner->token);
scanner->validtoken = 0;
}
}
/* With the token result table on the top of the stack, insert
a mark entry. */
static void
scanner_set_mark (lua_State *L, const char *k, yaml_mark_t mark)
{
lua_pushstring (L, k);
lua_createtable (L, 0, 3);
#define MENTRY(_s) RAWSET_INTEGER (#_s, mark._s)
MENTRY( index );
MENTRY( line );
MENTRY( column );
#undef MENTRY
lua_rawset (L, -3);
}
/* Push a new token table, pre-populated with shared elements. */
static void
scanner_push_tokentable (lyaml_scanner *scanner, const char *v, int n)
{
lua_State *L = scanner->L;
lua_createtable (L, 0, n + 3);
RAWSET_STRING ("type", v);
#define MENTRY(_s) scanner_set_mark (L, #_s, scanner->token._s)
MENTRY( start_mark );
MENTRY( end_mark );
#undef MENTRY
}
static void
scan_STREAM_START (lyaml_scanner *scanner)
{
#define EVENTF(_f) (scanner->token.data.stream_start._f)
lua_State *L = scanner->L;
const char *encoding;
switch (EVENTF (encoding))
{
#define MENTRY(_s) \
case YAML_##_s##_ENCODING: encoding = #_s; break
MENTRY( UTF8 );
MENTRY( UTF16LE );
MENTRY( UTF16BE );
#undef MENTRY
default:
lua_pushfstring (L, "invalid encoding %d", EVENTF (encoding));
lua_error (L);
}
scanner_push_tokentable (scanner, "STREAM_START", 1);
RAWSET_STRING ("encoding", encoding);
#undef EVENTF
}
static void
scan_VERSION_DIRECTIVE (lyaml_scanner *scanner)
{
#define EVENTF(_f) (scanner->token.data.version_directive._f)
lua_State *L = scanner->L;
scanner_push_tokentable (scanner, "VERSION_DIRECTIVE", 2);
#define MENTRY(_s) RAWSET_INTEGER (#_s, EVENTF (_s))
MENTRY( major );
MENTRY( minor );
#undef MENTRY
#undef EVENTF
}
static void
scan_TAG_DIRECTIVE (lyaml_scanner *scanner)
{
#define EVENTF(_f) (scanner->token.data.tag_directive._f)
lua_State *L = scanner->L;
scanner_push_tokentable (scanner, "TAG_DIRECTIVE", 2);
RAWSET_EVENTF( handle );
RAWSET_EVENTF( prefix );
#undef EVENTF
}
static void
scan_ALIAS (lyaml_scanner *scanner)
{
#define EVENTF(_f) (scanner->token.data.alias._f)
lua_State *L = scanner->L;
scanner_push_tokentable (scanner, "ALIAS", 1);
RAWSET_EVENTF (value);
#undef EVENTF
}
static void
scan_ANCHOR (lyaml_scanner *scanner)
{
#define EVENTF(_f) (scanner->token.data.anchor._f)
lua_State *L = scanner->L;
scanner_push_tokentable (scanner, "ANCHOR", 1);
RAWSET_EVENTF (value);
#undef EVENTF
}
static void
scan_TAG(lyaml_scanner *scanner)
{
#define EVENTF(_f) (scanner->token.data.tag._f)
lua_State *L = scanner->L;
scanner_push_tokentable (scanner, "TAG", 2);
RAWSET_EVENTF( handle );
RAWSET_EVENTF( suffix );
#undef EVENTF
}
static void
scan_SCALAR (lyaml_scanner *scanner)
{
#define EVENTF(_f) (scanner->token.data.scalar._f)
lua_State *L = scanner->L;
const char *style;
switch (EVENTF (style))
{
#define MENTRY(_s) \
case YAML_##_s##_SCALAR_STYLE: style = #_s; break
MENTRY( PLAIN );
MENTRY( SINGLE_QUOTED );
MENTRY( DOUBLE_QUOTED );
MENTRY( LITERAL );
MENTRY( FOLDED );
#undef MENTRY
default:
lua_pushfstring (L, "invalid scalar style %d", EVENTF (style));
lua_error (L);
}
scanner_push_tokentable (scanner, "SCALAR", 3);
RAWSET_EVENTF (value);
RAWSET_INTEGER ("length", EVENTF (length));
RAWSET_STRING ("style", style);
#undef EVENTF
}
static void
scanner_generate_error_message (lyaml_scanner *scanner)
{
yaml_parser_t *P = &scanner->parser;
char buf[256];
luaL_Buffer b;
luaL_buffinit (scanner->L, &b);
luaL_addstring (&b, P->problem ? P->problem : "A problem");
snprintf (buf, sizeof (buf), " at document: %d", scanner->document_count);
luaL_addstring (&b, buf);
if (P->problem_mark.line || P->problem_mark.column)
{
snprintf (buf, sizeof (buf), ", line: %lu, column: %lu",
(unsigned long) P->problem_mark.line + 1,
(unsigned long) P->problem_mark.column + 1);
luaL_addstring (&b, buf);
}
luaL_addstring (&b, "\n");
if (P->context)
{
snprintf (buf, sizeof (buf), "%s at line: %lu, column: %lu\n",
P->context,
(unsigned long) P->context_mark.line + 1,
(unsigned long) P->context_mark.column + 1);
luaL_addstring (&b, buf);
}
luaL_pushresult (&b);
}
static int
token_iter (lua_State *L)
{
lyaml_scanner *scanner = (lyaml_scanner *)lua_touserdata(L, lua_upvalueindex(1));
char *str;
scanner_delete_token (scanner);
if (yaml_parser_scan (&scanner->parser, &scanner->token) != 1)
{
scanner_generate_error_message (scanner);
return lua_error (L);
}
scanner->validtoken = 1;
lua_newtable (L);
lua_pushliteral (L, "type");
switch (scanner->token.type)
{
/* First the simple tokens, generated right here... */
#define MENTRY(_s) \
case YAML_##_s##_TOKEN: scanner_push_tokentable (scanner, #_s, 0); break
MENTRY( STREAM_END );
MENTRY( DOCUMENT_START );
MENTRY( DOCUMENT_END );
MENTRY( BLOCK_SEQUENCE_START );
MENTRY( BLOCK_MAPPING_START );
MENTRY( BLOCK_END );
MENTRY( FLOW_SEQUENCE_START );
MENTRY( FLOW_SEQUENCE_END );
MENTRY( FLOW_MAPPING_START );
MENTRY( FLOW_MAPPING_END );
MENTRY( BLOCK_ENTRY );
MENTRY( FLOW_ENTRY );
MENTRY( KEY );
MENTRY( VALUE );
#undef MENTRY
/* ...then the complex tokens, generated by a function call. */
#define MENTRY(_s) \
case YAML_##_s##_TOKEN: scan_##_s (scanner); break
MENTRY( STREAM_START );
MENTRY( VERSION_DIRECTIVE );
MENTRY( TAG_DIRECTIVE );
MENTRY( ALIAS );
MENTRY( ANCHOR );
MENTRY( TAG );
MENTRY( SCALAR );
#undef MENTRY
case YAML_NO_TOKEN:
lua_pushnil (L);
break;
default:
lua_pushfstring (L, "invalid token %d", scanner->token.type);
return lua_error (L);
}
return 1;
}
static int
scanner_gc (lua_State *L)
{
lyaml_scanner *scanner = (lyaml_scanner *) lua_touserdata (L, 1);
if (scanner)
{
scanner_delete_token (scanner);
yaml_parser_delete (&scanner->parser);
}
return 0;
}
void
scanner_init (lua_State *L)
{
luaL_newmetatable (L, "lyaml.scanner");
lua_pushcfunction (L, scanner_gc);
lua_setfield (L, -2, "__gc");
}
int
Pscanner (lua_State *L)
{
lyaml_scanner *scanner;
const unsigned char *str;
/* requires a single string type argument */
luaL_argcheck (L, lua_isstring (L, 1), 1, "must provide a string argument");
str = (const unsigned char *) lua_tostring (L, 1);
/* create a user datum to store the scanner */
scanner = (lyaml_scanner *) lua_newuserdata (L, sizeof (*scanner));
memset ((void *) scanner, 0, sizeof (*scanner));
scanner->L = L;
/* set its metatable */
luaL_getmetatable (L, "lyaml.scanner");
lua_setmetatable (L, -2);
/* try to initialize the scanner */
if (yaml_parser_initialize (&scanner->parser) == 0)
luaL_error (L, "cannot initialize parser for %s", str);
yaml_parser_set_input_string (&scanner->parser, str, lua_strlen (L, 1));
/* create and return the iterator function, with the loader userdatum as
its sole upvalue */
lua_pushcclosure (L, token_iter, 1);
return 1;
}

66
ext/yaml/yaml.c Normal file
View file

@ -0,0 +1,66 @@
/*
* yaml.c, LibYAML binding for Lua
* Written by Andrew Danforth, 2009
*
* Copyright (C) 2014-2022 Gary V. Vaughan
* Copyright (C) 2009 Andrew Danforth
*
* 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.
*
* Portions of this software were inspired by Perl's YAML::LibYAML module by
* Ingy döt Net <ingy@cpan.org>
*
*/
#include <string.h>
#include <stdlib.h>
#include <lualib.h>
#include "lyaml.h"
#define MYNAME "yaml"
#define MYVERSION MYNAME " library for " LUA_VERSION " / " VERSION
#define LYAML__STR_1(_s) (#_s + 1)
#define LYAML_STR_1(_s) LYAML__STR_1(_s)
static const luaL_Reg R[] =
{
#define MENTRY(_s) {LYAML_STR_1(_s), (_s)}
MENTRY( Pemitter ),
MENTRY( Pparser ),
MENTRY( Pscanner ),
#undef MENTRY
{NULL, NULL}
};
LUALIB_API int
luaopen_yaml (lua_State *L)
{
parser_init (L);
scanner_init (L);
luaL_register(L, "yaml", R);
lua_pushliteral(L, MYVERSION);
lua_setfield(L, -2, "version");
return 1;
}

120
lib/lyaml/explicit.lua Normal file
View file

@ -0,0 +1,120 @@
-- LYAML parse explicit token values.
-- Written by Gary V. Vaughan, 2015
--
-- Copyright(C) 2015-2022 Gary V. Vaughan
--
-- 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.
--- @module lyaml.explicit
local functional = require 'lyaml.functional'
local implicit = require 'lyaml.implicit'
local NULL = functional.NULL
local anyof = functional.anyof
local id = functional.id
local yn = {y=true, Y=true, n=false, N=false}
--- Parse the value following an explicit `!!bool` tag.
-- @function bool
-- @param value token
-- @treturn[1] bool boolean equivalent, if a valid value was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_bool = explicit.bool(tagarg)
local bool = anyof {
implicit.bool,
function(x) return yn[x] end,
}
--- Return a function that converts integer results to equivalent float.
-- @tparam function fn token parsing function
-- @treturn function new function that converts int results to float
-- @usage maybe_float = maybefloat(implicit.decimal)(tagarg)
local function maybefloat(fn)
return function(...)
local r = fn(...)
if type(r) == 'number' then
return r + 0.0
end
end
end
--- Parse the value following an explicit `!!float` tag.
-- @function float
-- @param value token
-- @treturn[1] number float equivalent, if a valid value was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_float = explicit.float(tagarg)
local float = anyof {
implicit.float,
implicit.nan,
implicit.inf,
maybefloat(implicit.octal),
maybefloat(implicit.decimal),
maybefloat(implicit.hexadecimal),
maybefloat(implicit.binary),
implicit.sexfloat,
}
--- Parse the value following an explicit `!!int` tag.
-- @function int
-- @param value token
-- @treturn[1] int integer equivalent, if a valid value was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_int = explicit.int(tagarg)
local int = anyof {
implicit.octal,
implicit.decimal,
implicit.hexadecimal,
implicit.binary,
implicit.sexagesimal,
}
--- Parse an explicit `!!null` tag.
-- @treturn lyaml.null
-- @usage null = explicit.null(tagarg)
local function null()
return NULL
end
--- Parse the value following an explicit `!!str` tag.
-- @function str
-- @tparam string value token
-- @treturn string *value* which was a string already
-- @usage tagarg = explicit.str(tagarg)
local str = id
--- @export
return {
bool = bool,
float = float,
int = int,
null = null,
str = str,
}

87
lib/lyaml/functional.lua Normal file
View file

@ -0,0 +1,87 @@
-- Minimal functional programming utilities.
-- Written by Gary V. Vaughan, 2015
--
-- Copyright(C) 2015-2022 Gary V. Vaughan
--
-- 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.
--- @module lyaml.functional
--- `lyaml.null` value.
-- @table NULL
local NULL = setmetatable({}, {_type='LYAML null'})
--- `lyaml.null` predicate.
-- @param x operand
-- @treturn bool `true` if *x* is `lyaml.null`.
local function isnull(x)
return(getmetatable(x) or {})._type == 'LYAML null'
end
--- Callable predicate.
-- @param x operand
-- @treturn bool `true` if *x* is a function has a __call metamethod
-- @usage r = iscallable(x) and x(...)
local function iscallable(x)
if type(x) ~= 'function' then
x =(getmetatable(x) or {}).__call
end
if type(x) == 'function' then
return x
end
end
--- Compose a function to try each callable with supplied args.
-- @tparam table fns list of functions to try
-- @treturn function a new function to call *...* functions, stopping
-- and returning the first non-nil result, if any
local function anyof(fns)
return function(...)
for _, fn in ipairs(fns) do
if iscallable(fn) then
local r = fn(...)
if r ~= nil then
return r
end
end
end
end
end
--- Return arguments unchanged.
-- @param ... arguments
-- @return *...*
local function id(...)
return ...
end
--- @export
return {
NULL = NULL,
anyof = anyof,
id = id,
iscallable = iscallable,
isnull = isnull,
}

283
lib/lyaml/implicit.lua Normal file
View file

@ -0,0 +1,283 @@
-- LYAML parse implicit type tokens.
-- Written by Gary V. Vaughan, 2015
--
-- Copyright(C) 2015-2022 Gary V. Vaughan
--
-- 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.
--- @module lyaml.implicit
local NULL = require 'lyaml.functional'.NULL
local find = string.find
local floor = math.floor
local gsub = string.gsub
local sub = string.sub
local tointeger = (function(f)
if not tointeger then
-- No host tointeger implementation, use our own.
return function(x)
if type(x) == 'number' and x - floor(x) == 0.0 then
return x
end
end
elseif f '1' ~= nil then
-- Don't perform implicit string-to-number conversion!
return function(x)
if type(x) == 'number' then
return tointeger(x)
end
end
end
-- Host tointeger is good!
return f
end)(math.tointeger)
local function int(x)
local r = tonumber(x)
if r ~= nil then
return tointeger(r)
end
end
local is_null = {['']=true, ['~']=true, null=true, Null=true, NULL=true}
--- Parse a null token to a null value.
-- @param value token
-- @return[1] lyaml.null, for an empty string or literal ~
-- @return[2] nil otherwise, nil
-- @usage maybe_null = implicit.null(token)
local function null(value)
if is_null[value] then
return NULL
end
end
local to_bool = {
['true'] = true, True = true, TRUE = true,
['false'] = false, False = false, FALSE = false,
yes = true, Yes = true, YES = true,
no = false, No = false, NO = false,
on = true, On = true, ON = true,
off = false, Off = false, OFF = false,
}
--- Parse a boolean token to the equivalent value.
-- Treats capilalized, lower and upper-cased variants of true/false,
-- yes/no or on/off tokens as boolean `true` and `false` values.
-- @param value token
-- @treturn[1] bool if a valid boolean token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_bool = implicit.bool(token)
local function bool(value)
return to_bool[value]
end
--- Parse a binary token, such as '0b1010\_0111\_0100\_1010\_1110'.
-- @tparam string value token
-- @treturn[1] int integer equivalent, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_int = implicit.binary(value)
local function binary(value)
local r
gsub(value, '^([+-]?)0b_*([01][01_]+)$', function(sign, rest)
r = 0
gsub(rest, '_*(.)', function(digit)
r = r * 2 + int(digit)
end)
if sign == '-' then
r = r * -1
end
end)
return r
end
--- Parse an octal token, such as '012345'.
-- @tparam string value token
-- @treturn[1] int integer equivalent, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_int = implicit.octal(value)
local function octal(value)
local r
gsub(value, '^([+-]?)0_*([0-7][0-7_]*)$', function(sign, rest)
r = 0
gsub(rest, '_*(.)', function(digit)
r = r * 8 + int(digit)
end)
if sign == '-' then
r = r * -1
end
end)
return r
end
--- Parse a decimal token, such as '0' or '12345'.
-- @tparam string value token
-- @treturn[1] int integer equivalent, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_int = implicit.decimal(value)
local function decimal(value)
local r
gsub(value, '^([+-]?)_*([0-9][0-9_]*)$', function(sign, rest)
rest = gsub(rest, '_', '')
if rest == '0' or #rest > 1 or sub(rest, 1, 1) ~= '0' then
r = int(rest)
if sign == '-' then
r = r * -1
end
end
end)
return r
end
--- Parse a hexadecimal token, such as '0xdeadbeef'.
-- @tparam string value token
-- @treturn[1] int integer equivalent, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_int = implicit.hexadecimal(value)
local function hexadecimal(value)
local r
gsub(value, '^([+-]?)(0x_*[0-9a-fA-F][0-9a-fA-F_]*)$', function(sign, rest)
rest = gsub(rest, '_', '')
r = int(rest)
if sign == '-' then
r = r * -1
end
end)
return r
end
--- Parse a sexagesimal token, such as '190:20:30'.
-- Useful for times and angles.
-- @tparam string value token
-- @treturn[1] int integer equivalent, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_int = implicit.sexagesimal(value)
local function sexagesimal(value)
local r
gsub(value, '^([+-]?)([0-9]+:[0-5]?[0-9][:0-9]*)$', function(sign, rest)
r = 0
gsub(rest, '([0-9]+):?', function(digit)
r = r * 60 + int(digit)
end)
if sign == '-' then
r = r * -1
end
end)
return r
end
local isnan = {['.nan']=true, ['.NaN']=true, ['.NAN']=true}
--- Parse a `nan` token.
-- @tparam string value token
-- @treturn[1] nan not-a-number, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_nan = implicit.nan(value)
local function nan(value)
if isnan[value] then
return 0/0
end
end
local isinf = {
['.inf'] = math.huge, ['.Inf'] = math.huge, ['.INF'] = math.huge,
['+.inf'] = math.huge, ['+.Inf'] = math.huge, ['+.INF'] = math.huge,
['-.inf'] = -math.huge, ['-.Inf'] = -math.huge, ['-.INF'] = -math.huge,
}
--- Parse a signed `inf` token.
-- @tparam string value token
-- @treturn[1] number plus/minus-infinity, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_inf = implicit.inf(value)
local function inf(value)
return isinf[value]
end
--- Parse a floating point number token, such as '1e-3' or '-0.12'.
-- @tparam string value token
-- @treturn[1] number float equivalent, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_float = implicit.float(value)
local function float(value)
local r = tonumber((gsub(value, '_', '')))
if r and find(value, '[%.eE]') then
return r
end
end
--- Parse a sexagesimal float, such as '190:20:30.15'.
-- Useful for times and angles.
-- @tparam string value token
-- @treturn[1] number float equivalent, if a valid token was recognized
-- @treturn[2] nil otherwise, nil
-- @usage maybe_float = implicit.sexfloat(value)
local function sexfloat(value)
local r
gsub(value, '^([+-]?)([0-9]+:[0-5]?[0-9][:0-9]*)(%.[0-9]+)$',
function(sign, rest, float)
r = 0
gsub(rest, '([0-9]+):?', function(digit)
r = r * 60 + int(digit)
end)
r = r + tonumber(float)
if sign == '-' then
r = r * -1
end
end
)
return r
end
--- @export
return {
binary = binary,
decimal = decimal,
float = float,
hexadecimal = hexadecimal,
inf = inf,
nan = nan,
null = null,
octal = octal,
sexagesimal = sexagesimal,
sexfloat = sexfloat,
bool = bool,
}

534
lib/lyaml/init.lua Normal file
View file

@ -0,0 +1,534 @@
-- Transform between YAML 1.1 streams and Lua table representations.
-- Written by Gary V. Vaughan, 2013
--
-- Copyright(C) 2013-2022 Gary V. Vaughan
--
-- 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.
--
-- Portions of this software were inspired by an earlier LibYAML binding
-- by Andrew Danforth <acd@weirdness.net>
--- @module lyaml
local explicit = require 'lyaml.explicit'
local functional = require 'lyaml.functional'
local implicit = require 'lyaml.implicit'
local yaml = require 'yaml'
local NULL = functional.NULL
local anyof = functional.anyof
local find = string.find
local format = string.format
local gsub = string.gsub
local id = functional.id
local isnull = functional.isnull
local match = string.match
local TAG_PREFIX = 'tag:yaml.org,2002:'
local function tag(name)
return TAG_PREFIX .. name
end
local default = {
-- Tag table to lookup explicit scalar conversions.
explicit_scalar = {
[tag 'bool'] = explicit.bool,
[tag 'float'] = explicit.float,
[tag 'int'] = explicit.int,
[tag 'null'] = explicit.null,
[tag 'str'] = explicit.str,
},
-- Order is important, so we put most likely and fastest nearer
-- the top to reduce average number of comparisons and funcalls.
implicit_scalar = anyof {
implicit.null,
implicit.octal, -- subset of decimal, must come earlier
implicit.decimal,
implicit.float,
implicit.bool,
implicit.inf,
implicit.nan,
implicit.hexadecimal,
implicit.binary,
implicit.sexagesimal,
implicit.sexfloat,
id,
},
}
-- Metatable for Dumper objects.
local dumper_mt = {
__index = {
-- Emit EVENT to the LibYAML emitter.
emit = function(self, event)
return self.emitter.emit(event)
end,
-- Look up an anchor for a repeated document element.
get_anchor = function(self, value)
local r = self.anchors[value]
if r then
self.aliased[value], self.anchors[value] = self.anchors[value], nil
end
return r
end,
-- Look up an already anchored repeated document element.
get_alias = function(self, value)
return self.aliased[value]
end,
-- Dump ALIAS into the event stream.
dump_alias = function(self, alias)
return self:emit {
type = 'ALIAS',
anchor = alias,
}
end,
-- Dump MAP into the event stream.
dump_mapping = function(self, map)
local alias = self:get_alias(map)
if alias then
return self:dump_alias(alias)
end
self:emit {
type = 'MAPPING_START',
anchor = self:get_anchor(map),
style = 'BLOCK',
}
for k, v in pairs(map) do
self:dump_node(k)
self:dump_node(v)
end
return self:emit {type='MAPPING_END'}
end,
-- Dump SEQUENCE into the event stream.
dump_sequence = function(self, sequence)
local alias = self:get_alias(sequence)
if alias then
return self:dump_alias(alias)
end
self:emit {
type = 'SEQUENCE_START',
anchor = self:get_anchor(sequence),
style = 'BLOCK',
}
for _, v in ipairs(sequence) do
self:dump_node(v)
end
return self:emit {type='SEQUENCE_END'}
end,
-- Dump a null into the event stream.
dump_null = function(self)
return self:emit {
type = 'SCALAR',
value = '~',
plain_implicit = true,
quoted_implicit = true,
style = 'PLAIN',
}
end,
-- Dump VALUE into the event stream.
dump_scalar = function(self, value)
local alias = self:get_alias(value)
if alias then
return self:dump_alias(alias)
end
local anchor = self:get_anchor(value)
local itsa = type(value)
local style = 'PLAIN'
if itsa == 'string' and self.implicit_scalar(value) ~= value then
-- take care to round-trip strings that look like scalars
style = 'SINGLE_QUOTED'
elseif value == math.huge then
value = '.inf'
elseif value == -math.huge then
value = '-.inf'
elseif value ~= value then
value = '.nan'
elseif itsa == 'number' or itsa == 'boolean' then
value = tostring(value)
elseif itsa == 'string' and find(value, '\n') then
style = 'LITERAL'
end
return self:emit {
type = 'SCALAR',
anchor = anchor,
value = value,
plain_implicit = true,
quoted_implicit = true,
style = style,
}
end,
-- Decompose NODE into a stream of events.
dump_node = function(self, node)
local itsa = type(node)
if isnull(node) then
return self:dump_null()
elseif itsa == 'string' or itsa == 'boolean' or itsa == 'number' then
return self:dump_scalar(node)
elseif itsa == 'table' then
-- Something is only a sequence if its keys start at 1
-- and are consecutive integers without any jumps.
local prior_key = 0
local is_pure_sequence = true
local i, v = next(node, nil)
while i and is_pure_sequence do
if type(i) ~= "number" or (prior_key + 1 ~= i) then
is_pure_sequence = false -- breaks the loop
else
prior_key = i
i, v = next(node, prior_key)
end
end
if is_pure_sequence then
-- Only sequentially numbered integer keys starting from 1.
return self:dump_sequence(node)
else
-- Table contains non sequential integer keys or mixed keys.
return self:dump_mapping(node)
end
else -- unsupported Lua type
error("cannot dump object of type '" .. itsa .. "'", 2)
end
end,
-- Dump DOCUMENT into the event stream.
dump_document = function(self, document)
self:emit {type='DOCUMENT_START'}
self:dump_node(document)
return self:emit {type='DOCUMENT_END'}
end,
},
}
-- Emitter object constructor.
local function Dumper(opts)
local anchors = {}
for k, v in pairs(opts.anchors) do
anchors[v] = k
end
local object = {
aliased = {},
anchors = anchors,
emitter = yaml.emitter(),
implicit_scalar = opts.implicit_scalar,
}
return setmetatable(object, dumper_mt)
end
--- Dump options table.
-- @table dumper_opts
-- @tfield table anchors map initial anchor names to values
-- @tfield function implicit_scalar parse implicit scalar values
--- Dump a list of Lua tables to an equivalent YAML stream.
-- @tparam table documents a sequence of Lua tables.
-- @tparam[opt] dumper_opts opts initialisation options
-- @treturn string equivalest YAML stream
local function dump(documents, opts)
opts = opts or {}
-- backwards compatibility
if opts.anchors == nil and opts.implicit_scalar == nil then
opts = {anchors=opts}
end
local dumper = Dumper {
anchors = opts.anchors or {},
implicit_scalar = opts.implicit_scalar or default.implicit_scalar,
}
dumper:emit {type='STREAM_START', encoding='UTF8'}
for _, document in ipairs(documents) do
dumper:dump_document(document)
end
local ok, stream = dumper:emit {type='STREAM_END'}
return stream
end
-- We save anchor types that will match the node type from expanding
-- an alias for that anchor.
local alias_type = {
MAPPING_END = 'MAPPING_END',
MAPPING_START = 'MAPPING_END',
SCALAR = 'SCALAR',
SEQUENCE_END = 'SEQUENCE_END',
SEQUENCE_START = 'SEQUENCE_END',
}
-- Metatable for Parser objects.
local parser_mt = {
__index = {
-- Return the type of the current event.
type = function(self)
return tostring(self.event.type)
end,
-- Raise a parse error.
error = function(self, errmsg, ...)
error(format('%d:%d: ' .. errmsg, self.mark.line,
self.mark.column, ...), 0)
end,
-- Save node in the anchor table for reference in future ALIASes.
add_anchor = function(self, node)
if self.event.anchor ~= nil then
self.anchors[self.event.anchor] = {
type = alias_type[self.event.type],
value = node,
}
end
end,
-- Fetch the next event.
parse = function(self)
local ok, event = pcall(self.next)
if not ok then
-- if ok is nil, then event is a parser error from libYAML
self:error(gsub(event, ' at document: .*$', ''))
end
self.event = event
self.mark = {
line = self.event.start_mark.line + 1,
column = self.event.start_mark.column + 1,
}
return self:type()
end,
-- Construct a Lua hash table from following events.
load_map = function(self)
local map = {}
self:add_anchor(map)
while true do
local key = self:load_node()
local tag = self.event.tag
if tag then
tag = match(tag, '^' .. TAG_PREFIX .. '(.*)$')
end
if key == nil then
break
end
if key == '<<' or tag == 'merge' then
tag = self.event.tag or key
local node, event = self:load_node()
if event == 'MAPPING_END' then
for k, v in pairs(node) do
if map[k] == nil then
map[k] = v
end
end
elseif event == 'SEQUENCE_END' then
for i, merge in ipairs(node) do
if type(merge) ~= 'table' then
self:error("invalid '%s' sequence element %d: %s",
tag, i, tostring(merge))
end
for k, v in pairs(merge) do
if map[k] == nil then
map[k] = v
end
end
end
else
if event == 'SCALAR' then
event = tostring(node)
end
self:error("invalid '%s' merge event: %s", tag, event)
end
else
local value, event = self:load_node()
if value == nil then
self:error('unexpected %s event', self:type())
end
map[key] = value
end
end
return map, self:type()
end,
-- Construct a Lua array table from following events.
load_sequence = function(self)
local sequence = {}
self:add_anchor(sequence)
while true do
local node = self:load_node()
if node == nil then
break
end
sequence[#sequence + 1] = node
end
return sequence, self:type()
end,
-- Construct a primitive type from the current event.
load_scalar = function(self)
local value = self.event.value
local tag = self.event.tag
local explicit = self.explicit_scalar[tag]
-- Explicitly tagged values.
if explicit then
value = explicit(value)
if value == nil then
self:error("invalid '%s' value: '%s'", tag, self.event.value)
end
-- Otherwise, implicit conversion according to value content.
elseif self.event.style == 'PLAIN' then
value = self.implicit_scalar(self.event.value)
end
self:add_anchor(value)
return value, self:type()
end,
load_alias = function(self)
local anchor = self.event.anchor
local event = self.anchors[anchor]
if event == nil then
self:error('invalid reference: %s', tostring(anchor))
end
return event.value, event.type
end,
load_node = function(self)
local dispatch = {
SCALAR = self.load_scalar,
ALIAS = self.load_alias,
MAPPING_START = self.load_map,
SEQUENCE_START = self.load_sequence,
MAPPING_END = function() end,
SEQUENCE_END = function() end,
DOCUMENT_END = function() end,
}
local event = self:parse()
if dispatch[event] == nil then
self:error('invalid event: %s', self:type())
end
return dispatch[event](self)
end,
},
}
-- Parser object constructor.
local function Parser(s, opts)
local object = {
anchors = {},
explicit_scalar = opts.explicit_scalar,
implicit_scalar = opts.implicit_scalar,
mark = {line=0, column=0},
next = yaml.parser(s),
}
return setmetatable(object, parser_mt)
end
--- Load options table.
-- @table loader_opts
-- @tfield boolean all load all documents from the stream
-- @tfield table explicit_scalar map full tag-names to parser functions
-- @tfield function implicit_scalar parse implicit scalar values
--- Load a YAML stream into a Lua table.
-- @tparam string s YAML stream
-- @tparam[opt] loader_opts opts initialisation options
-- @treturn table Lua table equivalent of stream *s*
local function load(s, opts)
opts = opts or {}
local documents = {}
local all = false
-- backwards compatibility
if opts == true then
opts = {all=true}
end
local parser = Parser(s, {
explicit_scalar = opts.explicit_scalar or default.explicit_scalar,
implicit_scalar = opts.implicit_scalar or default.implicit_scalar,
})
if parser:parse() ~= 'STREAM_START' then
error('expecting STREAM_START event, but got ' .. parser:type(), 2)
end
while parser:parse() ~= 'STREAM_END' do
local document = parser:load_node()
if document == nil then
error('unexpected ' .. parser:type() .. ' event')
end
if parser:parse() ~= 'DOCUMENT_END' then
error('expecting DOCUMENT_END event, but got ' .. parser:type(), 2)
end
-- save document
documents[#documents + 1] = document
-- reset anchor table
parser.anchors = {}
end
return opts.all and documents or documents[1]
end
--[[ ----------------- ]]--
--[[ Public Interface. ]]--
--[[ ----------------- ]]--
--- @export
return {
dump = dump,
load = load,
--- `lyaml.null` value.
-- @table null
null = NULL,
--- Version number from yaml C binding.
-- @table _VERSION
_VERSION = yaml.version,
}

47
lukefile Normal file
View file

@ -0,0 +1,47 @@
--[[
LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
Copyright (C) 2013-2022 Gary V. Vaughan
]]
package = 'lyaml'
version = '$USER'
defines = {
PACKAGE = '"$package"',
VERSION = '"$version"',
NDEBUG = 1,
_FORTIFY_SOURCE = 2,
platforms = {
aix = {_ALL_SOURCE = 1},
bsd = {_BSD_SOURCE = 1},
freebsd = {__BSD_VISIBLE = 1},
macosx = {_DARWIN_C_SOURCE = 1},
},
}
external_dependencies = {
YAML = {
library = {checksymbol='yaml_document_initialize', library='yaml'},
},
}
incdirs = {
'ext/include',
'$LUA_INCDIR',
}
ldocs = 'build-aux/config.ld.in'
modules = {
['yaml'] = {
'ext/yaml/yaml.c',
'ext/yaml/emitter.c',
'ext/yaml/parser.c',
'ext/yaml/scanner.c',
},
['lyaml'] = 'lib/lyaml/init.lua',
['lyaml.explicit'] = 'lib/lyaml/explicit.lua',
['lyaml.functional'] = 'lib/lyaml/functional.lua',
['lyaml.implicit'] = 'lib/lyaml/implicit.lua',
}

59
lyaml-6.2.8-1.rockspec Normal file
View file

@ -0,0 +1,59 @@
local _MODREV, _SPECREV = '6.2.8', '-1'
package = 'lyaml'
version = _MODREV .. _SPECREV
description = {
summary = 'libYAML binding for Lua',
detailed = 'Read and write YAML format files with Lua.',
homepage = 'http://github.com/gvvaughan/lyaml',
license = 'MIT/X11',
}
source = {
url = 'http://github.com/gvvaughan/lyaml/archive/v' .. _MODREV .. '.zip',
dir = 'lyaml-' .. _MODREV,
}
dependencies = {
'lua >= 5.1, < 5.5',
}
external_dependencies = {
YAML = {
library = 'yaml',
},
}
build = {
type = 'command',
build_command = '$(LUA) build-aux/luke'
.. ' package="' .. package .. '"'
.. ' version="' .. _MODREV .. '"'
.. ' PREFIX="$(PREFIX)"'
.. ' CFLAGS="$(CFLAGS)"'
.. ' LIBFLAG="$(LIBFLAG)"'
.. ' LIB_EXTENSION="$(LIB_EXTENSION)"'
.. ' OBJ_EXTENSION="$(OBJ_EXTENSION)"'
.. ' LUA="$(LUA)"'
.. ' LUA_DIR="$(LUADIR)"'
.. ' LUA_INCDIR="$(LUA_INCDIR)"'
.. ' YAML_DIR="$(YAML_DIR)"'
.. ' YAML_INCDIR="$(YAML_INCDIR)"'
.. ' YAML_LIBDIR="$(YAML_LIBDIR)"'
,
install_command = '$(LUA) build-aux/luke install --quiet'
.. ' INST_LIBDIR="$(LIBDIR)"'
.. ' INST_LUADIR="$(LUADIR)"'
,
copy_directories = {'doc'},
}
if _MODREV == 'git' then
build.copy_directories = nil
source = {
url = 'git://github.com/gvvaughan/lyaml.git',
}
end

View file

@ -0,0 +1,239 @@
# LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
# Copyright (C) 2013-2022 Gary V. Vaughan
specify emitting:
- it diagnoses an invalid event:
emitter = yaml.emitter ()
expect (emitter.emit "not an event").to_raise "expected table"
- it can generate an empty stream:
pending (github_issue "2")
expect (emit {
{type = "DOCUMENT_START", implicit = true},
{type = "SCALAR", value = ""},
{type = "DOCUMENT_END", implicit = true},
}).
to_equal ""
- describe STREAM_START:
- it diagnoses unrecognised encodings:
expect (emitevents (yaml.emitter (), {
{type = "STREAM_START", encoding = "notexists"},
"STREAM_END"})).
to_raise "invalid stream encoding 'notexists'"
- it accepts an encoding parameter:
expect (emitevents (yaml.emitter (), {
{type = "STREAM_START", encoding = "UTF16BE"},
"STREAM_END"})).
to_equal (BOM)
- describe STREAM_END:
- it returns the yaml document from the preceding events:
expect (emit {"DOCUMENT_START", {type = "SCALAR", value = "woo!"},
"DOCUMENT_END"}).
to_equal "--- woo!\n...\n"
- describe DOCUMENT_START:
- it accepts a version directive parameter:
expect (emit {{type = "DOCUMENT_START",
version_directive = { major = 1, minor = 1 }},
{type = "SCALAR", value = ""},
"DOCUMENT_END"}).
to_match "^%%YAML 1.1\n---"
- it accepts a list of tag directives:
expect (emit {{type = "DOCUMENT_START",
tag_directives = {{handle = "!",
prefix = "tag:ben-kiki.org,2000:app/"}}},
{type = "SCALAR", value = ""},
"DOCUMENT_END"}).
to_contain "%TAG ! tag:ben-kiki.org,2000:app/\n---"
expect (emit {
{type = "DOCUMENT_START",
tag_directives = {{handle = "!",
prefix = "tag:ben-kiki.org,2000:app/"},
{handle = "!!",
prefix = "tag:yaml.org,2002:"}}},
{type = "SCALAR", value = ""},
"DOCUMENT_END"}).
to_contain ("%TAG ! tag:ben-kiki.org,2000:app/\n" ..
"%TAG !! tag:yaml.org,2002:\n---")
- it accepts an implicit parameter:
expect (emit {{type = "DOCUMENT_START", implicit = true},
{type = "SCALAR", value = ""}, "DOCUMENT_END"}).
not_to_contain "--- \n"
pending (github_issue "2")
expect (emit {{type = "DOCUMENT_START", implicit = false},
{type = "SCALAR", value = ""}, "DOCUMENT_END"}).
not_to_contain "---"
- describe DOCUMENT_END:
- it accepts an implicit parameter:
expect (emit {"DOCUMENT_START", {type = "SCALAR", value = ""},
{type = "DOCUMENT_END", implicit = false}}).
to_contain "\n..."
pending (github_issue "2")
expect (emit {"DOCUMENT_START", {type = "SCALAR", value = ""},
{type = "DOCUMENT_END", implicit = true}}).
not_to_contain "\n..."
- describe MAPPING_START:
- it accepts an anchor parameter:
expect (emit {"DOCUMENT_START",
{type = "MAPPING_START", anchor = "foo"},
"MAPPING_END", "DOCUMENT_END"}).
to_contain "&foo"
- it diagnoses unrecognised styles:
expect (emit {"DOCUMENT_START",
{type = "MAPPING_START", style = "notexists"},
"MAPPING_END", "DOCUMENT_END"}).
to_raise "invalid mapping style 'notexists'"
- it understands block style: '
expect (emit {"DOCUMENT_START",
{type = "MAPPING_START", style = "BLOCK"},
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
"MAPPING_END", "DOCUMENT_END"}).
to_contain "foo: bar\n"'
- it understands flow style: '
expect (emit {"DOCUMENT_START",
{type = "MAPPING_START", style = "FLOW"},
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
{type = "SCALAR", value = "baz"}, {type = "SCALAR", value = "qux"},
"MAPPING_END", "DOCUMENT_END"}).
to_contain "{foo: bar, baz: qux}\n"'
- it accepts an explicit tag parameter: '
expect (emit {"DOCUMENT_START",
{type = "MAPPING_START", style = "FLOW",
tag = "tag:yaml.org,2002:map", implicit = false},
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
"MAPPING_END", "DOCUMENT_END"}).
to_contain "!!map {foo: bar}"'
- it accepts an implicit tag parameter: '
expect (emit {"DOCUMENT_START",
{type = "MAPPING_START", tag = "tag:yaml.org,2002:map", implicit = true},
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
"MAPPING_END", "DOCUMENT_END"}).
not_to_contain "map"'
- describe MAPPING_END:
- it requires no parameters: '
expect (emit {"DOCUMENT_START", "MAPPING_START",
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
"MAPPING_END", "DOCUMENT_END"}).
to_contain "foo: bar\n"'
- describe SEQUENCE_START:
- it accepts an anchor parameter:
expect (emit {"DOCUMENT_START",
{type = "SEQUENCE_START", anchor = "foo"},
"SEQUENCE_END", "DOCUMENT_END"}).
to_contain "&foo"
- it diagnoses unrecognised styles:
expect (emit {"DOCUMENT_START",
{type = "SEQUENCE_START", style = "notexists"},
"SEQUENCE_END", "DOCUMENT_END"}).
to_raise "invalid sequence style 'notexists'"
- it understands block style:
expect (emit {"DOCUMENT_START",
{type = "SEQUENCE_START", style = "BLOCK"},
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
"SEQUENCE_END", "DOCUMENT_END"}).
to_contain "- foo\n- bar\n"
- it understands flow style:
expect (emit {"DOCUMENT_START",
{type = "SEQUENCE_START", style = "FLOW"},
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
"SEQUENCE_END", "DOCUMENT_END"}).
to_contain "[foo, bar]"
- it accepts an explicit tag parameter:
expect (emit {"DOCUMENT_START",
{type = "SEQUENCE_START", style = "FLOW",
tag = "tag:yaml.org,2002:sequence", implicit = false},
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
"SEQUENCE_END", "DOCUMENT_END"}).
to_contain "!!sequence [foo, bar]\n"
- it accepts an implicit tag parameter:
expect (emit {"DOCUMENT_START",
{type = "SEQUENCE_START", style = "FLOW",
tag = "tag:yaml.org,2002:sequence", implicit = true},
{type = "SCALAR", value = "foo"}, {type = "SCALAR", value = "bar"},
"SEQUENCE_END", "DOCUMENT_END"}).
not_to_contain "sequence"
- describe SEQUENCE_END:
- it requires no parameters: '
expect (emit {"DOCUMENT_START", "SEQUENCE_START",
{type = "SCALAR", value = "moo"},
"SEQUENCE_END", "DOCUMENT_END"}).
to_contain "- moo\n"'
- describe SCALAR:
- it diagnoses a missing value parameter:
- it accepts a value parameter:
expect (emit {"DOCUMENT_START", {type = "SCALAR", value = "boo"},
"DOCUMENT_END"}).
to_contain "boo"
- it diagnoses unrecognised styles:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "notexists", value = "foo"},
"DOCUMENT_END"}).
to_raise "invalid scalar style 'notexists'"
- it understands plain style:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "PLAIN", value = "boo"},
"DOCUMENT_END"}).
to_contain "boo\n"
- it understands single quoted style:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "SINGLE_QUOTED", value = "bar"},
"DOCUMENT_END"}).
to_contain "'bar'\n"
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "SINGLE_QUOTED", value = "bar'"},
"DOCUMENT_END"}).
to_contain "'bar'''\n"
- it understands double quoted style:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "DOUBLE_QUOTED", value = "baz"},
"DOCUMENT_END"}).
to_contain '"baz"\n'
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "DOUBLE_QUOTED", value = '"baz"'},
"DOCUMENT_END"}).
to_contain ([["\"baz\""]] .. "\n")
- it understands literal style:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "LITERAL", value = "quux"},
"DOCUMENT_END"}).
to_contain "|-\n quux\n"
- it understands folded style:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "FOLDED", value = "thud"},
"DOCUMENT_END"}).
to_contain ">-\n thud\n"
- it understands plain_implicit:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "PLAIN", value = "hello", plain_implicit=false},
"DOCUMENT_END"}).
to_contain "'hello'\n"
- it understands quoted_implicit:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "PLAIN", value = "- world", quoted_implicit=false},
"DOCUMENT_END"}).
to_contain "! '- world'\n"
- it understands tag:
expect (emit {"DOCUMENT_START",
{type = "SCALAR", style = "PLAIN", value = "bug_squash", tag="tagger", plain_implicit=false, quoted_implicit=false},
"DOCUMENT_END"}).
to_contain "!<tagger> bug_squash\n"
- describe ALIAS:
- it diagnoses missing anchor parameter:
- it diagnoses non-alphanumeric anchor characters:
expect (emit {"DOCUMENT_START", {type = "ALIAS", anchor = "woo!"},
"DOCUMENT_END"}).
to_raise "must contain alphanumerical characters only"
- it accepts an anchor parameter:
expect (emit {"DOCUMENT_START", "SEQUENCE_START",
{type = "SCALAR", anchor = "woo", value = "hoo"},
{type = "ALIAS", anchor = "woo"},
"SEQUENCE_END", "DOCUMENT_END"}).
to_contain.all_of {"&woo", "*woo"}

View file

@ -0,0 +1,391 @@
# LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
# Copyright (C) 2013-2022 Gary V. Vaughan
specify parsing:
- it parses empty streams:
e = yaml.parser ""
expect (e ().type).to_be "STREAM_START"
expect (e ().type).to_be "STREAM_END"
expect (e ()).to_be (nil)
expect (e ()).to_be (nil)
- it ignores comments: '
e = yaml.parser "# A comment\nnon-comment # trailing comment\n"
expect (e ().type).to_be "STREAM_START"
expect (e ().type).to_be "DOCUMENT_START"
expect (e ().value).to_be "non-comment"
expect (e ().type).to_be "DOCUMENT_END"'
- describe STREAM_START:
- before:
e = yaml.parser "# no BOM"
- it is the first event:
expect (e ().type).to_be "STREAM_START"
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 0, column = 0, index = 0}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 0, column = 0, index = 0}
- it uses UTF-8 by default:
expect (e ().encoding).to_be "UTF8"
- it recognizes UTF-16 BOM:
e = yaml.parser (BOM .. " BOM")
expect (e ().encoding).to_match "UTF16[BL]E"
- describe STREAM_END:
- before:
for t in yaml.parser "nothing to see" do ev = t end
- it is the last event:
expect (ev.type).to_be "STREAM_END"
- it reports event start marker:
expect (ev.start_mark).to_equal {line = 1, column = 0, index = 14}
- it reports event end marker:
expect (ev.end_mark).to_equal {line = 1, column = 0, index = 14}
- describe DOCUMENT_START:
- before:
e = consume (1, "---")
- it recognizes document start marker:
expect (filter (e (), "type", "implicit")).
to_equal {type = "DOCUMENT_START", implicit = false}
- it reports implicit document start:
e = consume (1, "foo")
expect (e ().implicit).to_be (true)
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 0, column = 0, index = 0}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 0, column = 3, index = 3}
- context parser directives:
- it can recognize document versions:
e = consume (1, "%YAML 1.1\n---")
expect (e ().version_directive).to_equal {major = 1, minor = 1}
- it can diagnose missing document start:
e = consume (1, "%YAML 1.1\n")
expect (e ()).to_error "expected <document start>"
- it can diagnose multiple versions:
e = consume (1, "%YAML 1.1\n%YAML 1.1\n---")
expect (e ()).to_error "duplicate %YAML directive"
- it can diagnose too-new versions:
e = consume (1, "%YAML 2.0\n---")
expect (e ()).to_error "incompatible YAML document"
- it warns of newer minor versions:
pending (github_issue "1")
e = consume (1, "%YAML 1.9\n---")
expect (e ()).
to_error "attempting parsing of newer minor document version"
- it can recognize primary tag handles:
e = consume (1, "%TAG ! tag:ben-kiki.org,2000:app/\n---")
expect (e ().tag_directives).
to_equal {{handle = "!", prefix = "tag:ben-kiki.org,2000:app/"}}
- it can recognize secondary tag handles:
e = consume (1, "%TAG !! tag:yaml.org,2002:\n---")
expect (e ().tag_directives).
to_equal {{handle = "!!", prefix = "tag:yaml.org,2002:"}}
- it can recognize named tag handles:
e = consume (1, "%TAG !o! tag:ben-kiki.org,2000:\n---")
expect (e ().tag_directives).
to_equal {{handle = "!o!", prefix = "tag:ben-kiki.org,2000:"}}
- it can concatenate multiple tag handles:
e = consume (1, "%TAG ! !\n" ..
"%TAG !! tag:yaml.org,2002:\n" ..
"%TAG !o! tag:ben-kiki.org,2000:\n" ..
"---")
expect (e ().tag_directives).to_contain.
all_of {{handle = "!", prefix = "!"},
{handle = "!!", prefix = "tag:yaml.org,2002:"},
{handle = "!o!", prefix = "tag:ben-kiki.org,2000:"}}
- it can diagnose missing document start:
e = consume (1, "%TAG ! !\n")
expect (e ()).to_error "expected <document start>"
- describe DOCUMENT_END:
- before:
e = consume (3, "foo\n...")
- it recognizes the document end marker:
expect (filter (e (), "type", "implicit")).
to_equal {type = "DOCUMENT_END", implicit = false}
- it reports an implicit document end marker:
e = consume (3, "foo\n")
expect (filter (e (), "type", "implicit")).
to_equal {type = "DOCUMENT_END", implicit = true}
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 1, column = 0, index = 4}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 1, column = 3, index = 7}
- describe ALIAS:
- before:
e = consume (10, "---\n" ..
"hr:\n" ..
"- Mark McGwire\n" ..
"- &SS Sammy Sosa\n" ..
"rbi:\n" ..
"- *SS\n" ..
"- Ken Griffey")
- it recognizes an alias event:
expect (filter (e (), "type", "anchor")).
to_equal {type = "ALIAS", anchor = "SS"}
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 5, column = 2, index = 47}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 5, column = 5, index = 50}
- describe SCALAR:
- before:
e = consume (6, "---\n" ..
"hr:\n" ..
"- Mark McGwire\n" ..
"- &SS Sammy Sosa\n" ..
"rbi:\n" ..
"- *SS\n" ..
"- Ken Griffey")
- it recognizes a scalar event:
expect (filter (e (), "type", "value")).
to_equal {type = "SCALAR", value = "Sammy Sosa"}
- it records anchors:
expect (e ().anchor).to_be "SS"
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 3, column = 2, index = 25}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 3, column = 16, index = 39}
- context with quoting style:
- context plain style:
- before:
e = consume (2, "---\n" ..
" Mark McGwire's\n" ..
" year was crippled\n" ..
" by a knee injury.\n")
- it ignores line-breaks and indentation:
expect (e ().value).
to_be "Mark McGwire's year was crippled by a knee injury."
- it recognizes implicit plain style:
e = consume (2, "---\n" ..
" Mark McGwire's\n" ..
" year was crippled\n" ..
" by a knee injury.\n")
expect (e ().plain_implicit).to_be (true)
- it recognizes explicit plain style:
e = consume (2, "|\n" ..
" Mark McGwire's\n" ..
" year was crippled\n" ..
" by a knee injury.\n")
expect (e ().plain_implicit).to_be (false)
- it recognizes implicit quoted style:
e = consume (2, "|\n" ..
" Mark McGwire's\n" ..
" year was crippled\n" ..
" by a knee injury.\n")
expect (e ().quoted_implicit).to_be (true)
- it recognizes explicit quoted style:
e = consume (2, "'\n" ..
" Mark McGwire's\n" ..
" year was crippled\n" ..
" by a knee injury.'\n")
expect (e ().plain_implicit).to_be (false)
- context folded style:
- it preserves blank lines and deeper indentation:
e = consume (2, ">\n" ..
" Sammy Sosa completed another\n" ..
" fine season with great stats.\n" ..
"\n" ..
" 63 Home Runs\n" ..
" 0.288 Batting Average\n" ..
"\n" ..
" What a year!\n")
expect (e ().value).
to_be ("Sammy Sosa completed another fine season with great stats.\n" ..
"\n" ..
" 63 Home Runs\n" ..
" 0.288 Batting Average\n" ..
"\n" ..
"What a year!\n")
- context literal style:
- it removes indentation but preserves all line-breaks:
e = consume (2, [[# ASCII Art]] .. "\n" ..
[[--- |]] .. "\n" ..
[[ \//||\/||]] .. "\n" ..
[[ // || ||__]] .. "\n")
expect (e ().value).
to_be ([[\//||\/||]] .. "\n" ..
[[// || ||__]] .. "\n")
- context single quoted style:
- it folds line breaks:
e = consume (2, [['This quoted scalar]] .. "\n" ..
[[ spans two lines.']])
expect (e ().value).
to_be "This quoted scalar spans two lines."
- it does not process escape sequences:
# Lua [[ quoting makes sure libyaml sees all the quotes.
e = consume (2, [['"Howdy!"\t\u263A']])
expect (e ().value).to_be [["Howdy!"\t\u263A]]
# Note that we have to single quote the Lua snippets to prevent
# libyaml from interpreting the bytes as the spec file is read, so
# that the raw strings get correctly passed to the Lua compiler.
- context double quoted style:
- it folds line breaks: '
e = consume (4, [[quoted: "This quoted scalar]] .. "\n" ..
[[ spans two lines\n"]])
expect (e ().value).
to_be "This quoted scalar spans two lines\n"'
- it recognizes unicode escape sequences: '
e = consume (4, [[unicode: "Sosa did fine.\u263A"]])
expect (e ().value).to_be "Sosa did fine.\226\152\186"'
- it recognizes control escape sequences: '
e = consume (4, [[control: "\b1998\t1999\t2000\n"]])
expect (e ().value).to_be "\b1998\t1999\t2000\n"'
- it recognizes hexadecimal escape sequences: '
e = consume (4, [[hexesc: "\x41\x42\x43 is ABC"]])
expect (e ().value).to_be "ABC is ABC"'
- context indentation determines scope: '
e = consume (4, "name: Mark McGwire\n" ..
"accomplishment: >\n" ..
" Mark set a major league\n" ..
" home run record in 1998.\n" ..
"stats: |\n" ..
" 65 Home Runs\n" ..
" 0.278 Batting Average\n")
expect (e ().value).to_be "Mark McGwire"
expect (e ().value).to_be "accomplishment"
expect (e ().value).
to_be "Mark set a major league home run record in 1998.\n"
expect (e ().value).to_be "stats"
expect (e ().value).to_be "65 Home Runs\n0.278 Batting Average\n"'
- context with tag:
- it recognizes local tags: '
e = consume (4, "application specific tag: !something |\n" ..
" The semantics of the tag\n" ..
" above may be different for\n" ..
" different documents.")
expect (e ().tag).to_be "!something"'
- it recognizes global tags: '
e = consume (4, "picture: !!binary |\n" ..
" R0lGODlhDAAMAIQAAP//9/X\n" ..
" 17unp5WZmZgAAAOfn515eXv\n" ..
" Pz7Y6OjuDg4J+fn5OTk6enp\n" ..
" 56enmleECcgggoBADs=")
expect (e ().tag).to_be "tag:yaml.org,2002:binary"'
- it resolves %TAG declarations: '
e = consume (5, "%TAG ! tag:clarkevans.com,2002:\n" ..
"---\n" ..
"shape:\n" ..
"- !circle\n" ..
" center: &ORIGIN {x: 73, y: 129}\n" ..
" radius: 7")
expect (e ().tag).to_be "tag:clarkevans.com,2002:circle"'
- describe SEQUENCE_START:
- before: '
e = consume (4, "fubar: &FOO\n" ..
" - foo\n" ..
" - bar\n")'
- it recognizes a sequence start event:
expect (e ().type).to_be "SEQUENCE_START"
- it records anchors:
expect (e ().anchor).to_be "FOO"
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 0, column = 7, index = 7}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 1, column = 2, index = 14}
- context with tag:
- it recognizes local tags: '
e = consume (2, "--- !something\n" ..
"- foo\n")
expect (filter (e (), "type", "tag")).
to_equal {type = "SEQUENCE_START", tag = "!something"}'
- it recognizes global tags: '
e = consume (2, "--- !!omap\n" ..
"- Mark McGwire: 65\n" ..
"- Sammy Sosa: 63\n" ..
"- Ken Griffy: 58\n")
expect (filter (e (), "type", "tag")).
to_equal {type = "SEQUENCE_START",
tag = "tag:yaml.org,2002:omap"}'
- it resolves %TAG declarations: '
e = consume (2, "%TAG ! tag:clarkevans.com,2002:\n" ..
"--- !shape\n" ..
"- !circle\n" ..
" center: &ORIGIN {x: 73, y: 129}\n" ..
" radius: 7\n")
expect (filter (e (), "type", "tag")).
to_equal {type = "SEQUENCE_START",
tag = "tag:clarkevans.com,2002:shape"}'
- context with style:
- it recognizes block style:
e = consume (2, "- first\n- second")
expect (filter (e (), "type", "style")).
to_equal {type = "SEQUENCE_START", style = "BLOCK"}
- it recognizes flow style:
e = consume (2, "[first, second]")
expect (filter (e (), "type", "style")).
to_equal {type = "SEQUENCE_START", style = "FLOW"}
- describe SEQUENCE_END:
- before:
e = consume (5, "- foo\n- bar\n")
- it recognizes a sequence end event:
expect (e ().type).to_equal "SEQUENCE_END"
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 2, column = 0, index = 12}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 2, column = 0, index = 12}
- describe MAPPING_START:
- before: 'e = consume (3, "- &FUBAR\n foo: bar\n")'
- it recognizes a mapping start event:
expect (e ().type).to_be "MAPPING_START"
- it records anchors:
expect (e ().anchor).to_be "FUBAR"
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 0, column = 2, index = 2}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 1, column = 2, index = 11}
- context with tag:
- it recognizes local tags: '
e = consume (2, "--- !something\nfoo: bar\n")
expect (filter (e (), "type", "tag")).
to_equal {type = "MAPPING_START", tag = "!something"}'
- it recognizes global tags: '
e = consume (2, "--- !!set\n" ..
"? Mark McGwire\n" ..
"? Sammy Sosa\n" ..
"? Ken Griffy\n")
expect (filter (e (), "type", "tag")).
to_equal {type = "MAPPING_START",
tag = "tag:yaml.org,2002:set"}'
- it resolves %TAG declarations: '
e = consume (3, "%TAG ! tag:clarkevans.com,2002:\n" ..
"--- !shape\n" ..
"- !circle\n" ..
" center: &ORIGIN {x: 73, y: 129}\n" ..
" radius: 7\n")
expect (filter (e (), "type", "tag")).
to_equal {type = "MAPPING_START",
tag = "tag:clarkevans.com,2002:circle"}'
- context with style:
- it recognizes block style: '
e = consume (2, "foo: bar\nbaz:\n quux")
expect (filter (e (), "type", "style")).
to_equal {type = "MAPPING_START", style = "BLOCK"}'
- it recognizes flow style: '
e = consume (2, "{foo: bar, baz: quux}")
expect (filter (e (), "type", "style")).
to_equal {type = "MAPPING_START", style = "FLOW"}'
- describe MAPPING_END:
- before: 'e = consume (5, "foo: bar\n")'
- it recognizes the mapping end event:
expect (e ().type).to_equal "MAPPING_END"
- it reports event start marker:
expect (e ().start_mark).to_equal {line = 1, column = 0, index = 9}
- it reports event end marker:
expect (e ().end_mark).to_equal {line = 1, column = 0, index = 9}

View file

@ -0,0 +1,380 @@
# LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
# Copyright (C) 2013-2022 Gary V. Vaughan
before:
function consume (n, str)
local k = yaml.scanner (str)
for n = 1, n do k () end
return k
end
specify scanning:
- it scans empty streams:
k = yaml.scanner ""
expect (k ().type).to_be "STREAM_START"
expect (k ().type).to_be "STREAM_END"
expect (k ()).to_be (nil)
expect (k ()).to_be (nil)
- it ignores comments: '
k = yaml.scanner "# A comment\nnon-comment # trailing comment\n"
expect (k ().type).to_be "STREAM_START"
expect (k ().value).to_be "non-comment"
expect (k ().type).to_be "STREAM_END"'
- describe STREAM_START:
- before:
k = yaml.scanner "# no BOM"
- it is the first token:
expect (k ().type).to_be "STREAM_START"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 0, index = 0}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 0, index = 0}
- it uses UTF-8 by default:
expect (k ().encoding).to_be "UTF8"
- it recognizes UTF-16 BOM:
k = yaml.scanner (BOM .. " BOM")
expect (k ().encoding).to_match "UTF16[BL]E"
- describe STREAM_END:
- before:
for t in yaml.scanner "nothing to see" do k = t end
- it is the last token:
expect (k.type).to_be "STREAM_END"
- it reports token start marker:
expect (k.start_mark).to_equal {line = 1, column = 0, index = 14}
- it reports token end marker:
expect (k.end_mark).to_equal {line = 1, column = 0, index = 14}
- describe VERSION_DIRECTIVE:
- before:
k = consume (1, "%YAML 1.0")
- it can recognize document versions:
expect (filter (k (), "type", "major", "minor")).
to_equal {type = "VERSION_DIRECTIVE", major = 1, minor = 0}
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 0, index = 0}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 9, index = 9}
- describe TAG_DIRECTIVE:
- it can recognize primary tag handles:
k = consume (1, "%TAG ! tag:ben-kiki.org,2000:app/")
expect (filter (k (), "handle", "prefix")).
to_equal {handle = "!", prefix = "tag:ben-kiki.org,2000:app/"}
- it can recognize secondary tag handles:
k = consume (1, "%TAG !! tag:yaml.org,2002:")
expect (filter (k (), "handle", "prefix")).
to_equal {handle = "!!", prefix = "tag:yaml.org,2002:"}
- it can recognize named tag handles:
k = consume (1, "%TAG !o! tag:ben-kiki.org,2000:\n---")
expect (filter (k (), "handle", "prefix")).
to_equal {handle = "!o!", prefix = "tag:ben-kiki.org,2000:"}
- describe DOCUMENT_START:
- before:
k = consume (1, "---")
- it recognizes document start marker:
expect (k ().type).to_be "DOCUMENT_START"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 0, index = 0}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 3, index = 3}
- describe DOCUMENT_END:
- before:
k = consume (2, "foo\n...")
- it recognizes the document end marker:
expect (k ().type).to_be "DOCUMENT_END"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 1, column = 0, index = 4}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 1, column = 3, index = 7}
- describe ALIAS:
- before:
k = consume (15, "---\n" ..
"hr:\n" ..
"- Mark McGwire\n" ..
"- &SS Sammy Sosa\n" ..
"rbi:\n" ..
"- *SS\n" ..
"- Ken Griffey")
- it recognizes an alias token:
expect (filter (k (), "type", "value")).
to_equal {type = "ALIAS", value = "SS"}
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 5, column = 2, index = 47}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 5, column = 5, index = 50}
- describe ANCHOR:
- before:
k = consume (9, "---\n" ..
"hr:\n" ..
"- Mark McGwire\n" ..
"- &SS Sammy Sosa\n" ..
"rbi:\n" ..
"- *SS\n" ..
"- Ken Griffey")
- it recognizes an anchor token:
expect (filter (k (), "type", "value")).
to_equal {type = "ANCHOR", value = "SS"}
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 3, column = 2, index = 25}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 3, column = 5, index = 28}
- describe SCALAR:
- before:
k = consume (10, "---\n" ..
"hr:\n" ..
"- Mark McGwire\n" ..
"- &SS Sammy Sosa\n" ..
"rbi:\n" ..
"- *SS\n" ..
"- Ken Griffey")
- it recognizes a scalar token:
expect (filter (k (), "type", "value")).
to_equal {type = "SCALAR", value = "Sammy Sosa"}
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 3, column = 6, index = 29}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 3, column = 16, index = 39}
- context with quoting style:
- context plain style:
- before:
k = consume (2, "---\n" ..
" Mark McGwire's\n" ..
" year was crippled\n" ..
" by a knee injury.\n")
- it ignores line-breaks and indentation:
expect (k ().value).
to_be "Mark McGwire's year was crippled by a knee injury."
- it recognizes PLAIN style:
expect (k ().style).to_be "PLAIN"
- context folded style:
- before:
k = consume (1, ">\n" ..
" Sammy Sosa completed another\n" ..
" fine season with great stats.\n" ..
"\n" ..
" 63 Home Runs\n" ..
" 0.288 Batting Average\n" ..
"\n" ..
" What a year!\n")
- it preserves blank lines and deeper indentation:
expect (k ().value).
to_be ("Sammy Sosa completed another fine season with great stats.\n" ..
"\n" ..
" 63 Home Runs\n" ..
" 0.288 Batting Average\n" ..
"\n" ..
"What a year!\n")
- it recognizes FOLDED style:
expect (k ().style).to_be "FOLDED"
- context literal style:
- before:
k = consume (2, [[# ASCII Art]] .. "\n" ..
[[--- |]] .. "\n" ..
[[ \//||\/||]] .. "\n" ..
[[ // || ||__]] .. "\n")
- it removes indentation but preserves all line-breaks:
expect (k ().value).
to_be ([[\//||\/||]] .. "\n" ..
[[// || ||__]] .. "\n")
- it recognizes LITERAL style:
expect (k ().style).to_be "LITERAL"
- context single quoted style:
- before:
k = consume (1, [['This quoted scalar]] .. "\n" ..
[[ spans two lines.']])
- it folds line breaks:
expect (k ().value).
to_be "This quoted scalar spans two lines."
- it does not process escape sequences:
# Lua [[ quoting makes sure libyaml sees all the quotes.
k = consume (1, [['"Howdy!"\t\u263A']])
expect (k ().value).to_be [["Howdy!"\t\u263A]]
- it recognizes LITERAL style:
expect (k ().style).to_be "SINGLE_QUOTED"
# Note that we have to single quote the Lua snippets to prevent
# libyaml from interpreting the bytes as the spec file is read, so
# that the raw strings get correctly passed to the Lua compiler.
- context double quoted style:
- it folds line breaks: '
k = consume (5, [[quoted: "This quoted scalar]] .. "\n" ..
[[ spans two lines\n"]])
expect (k ().value).
to_be "This quoted scalar spans two lines\n"'
- it recognizes unicode escape sequences: '
k = consume (5, [[unicode: "Sosa did fine.\u263A"]])
expect (k ().value).to_be "Sosa did fine.\226\152\186"'
- it recognizes control escape sequences: '
k = consume (5, [[control: "\b1998\t1999\t2000\n"]])
expect (k ().value).to_be "\b1998\t1999\t2000\n"'
- it recognizes hexadecimal escape sequences: '
k = consume (5, [[hexesc: "\x41\x42\x43 is ABC"]])
expect (k ().value).to_be "ABC is ABC"'
- context indentation determines scope: '
k = consume (5, "name: Mark McGwire\n" ..
"accomplishment: >\n" ..
" Mark set a major league\n" ..
" home run record in 1998.\n" ..
"stats: |\n" ..
" 65 Home Runs\n" ..
" 0.278 Batting Average\n")
expect (k ().value).to_be "Mark McGwire"
expect (k ().type).to_be "KEY"
expect (k ().value).to_be "accomplishment"
expect (k ().type).to_be "VALUE"
expect (k ().value).
to_be "Mark set a major league home run record in 1998.\n"
expect (k ().type).to_be "KEY"
expect (k ().value).to_be "stats"
expect (k ().type).to_be "VALUE"
expect (k ().value).to_be "65 Home Runs\n0.278 Batting Average\n"'
- describe TAG:
- it recognizes local tags: '
k = consume (5, "application specific tag: !something |\n" ..
" The semantics of the tag\n" ..
" above may be different for\n" ..
" different documents.")
expect (filter (k (), "type", "handle", "suffix")).
to_equal {type = "TAG", handle = "!", suffix = "something"}'
- it recognizes global tags: '
k = consume (5, "picture: !!binary |\n" ..
" R0lGODlhDAAMAIQAAP//9/X\n" ..
" 17unp5WZmZgAAAOfn515eXv\n" ..
" Pz7Y6OjuDg4J+fn5OTk6enp\n" ..
" 56enmleECcgggoBADs=")
expect (filter (k (), "type", "handle", "suffix")).
to_equal {type = "TAG", handle = "!!", suffix = "binary"}'
- describe BLOCK_SEQUENCE_START:
- before: '
k = consume (5, "fubar:\n" ..
" - foo\n" ..
" - bar\n")'
- it recognizes a sequence start token:
expect (k ().type).to_be "BLOCK_SEQUENCE_START"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 1, column = 2, index = 9}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 1, column = 2, index = 9}
- describe BLOCK_MAPPING_START:
- before: 'k = consume (3, "-\n foo: bar\n-")'
- it recognizes a mapping start token:
expect (k ().type).to_be "BLOCK_MAPPING_START"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 1, column = 2, index = 4}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 1, column = 2, index = 4}
- describe BLOCK_ENTRY:
- before: 'k = consume (2, "-\n foo: bar\n-")'
- it recognizes a sequence block entry token: '
k = consume (8, "fubar:\n" ..
" - foo\n" ..
" - bar\n")
expect (k ().type).to_be "BLOCK_ENTRY"'
- it recognizes a mapping block entry token:
expect (k ().type).to_be "BLOCK_ENTRY"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 0, index = 0}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 1, index = 1}
- describe BLOCK_END:
- before: 'k = consume (8, "-\n foo: bar\n-")'
- it recognizes a sequence block end token: '
k = consume (10, "fubar:\n" ..
" - foo\n" ..
" - bar\n")
expect (k ().type).to_be "BLOCK_END"'
- it recognizes a mapping block end token:
expect (k ().type).to_be "BLOCK_END"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 2, column = 0, index = 13}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 2, column = 0, index = 13}
- describe FLOW_SEQUENCE_START:
- before: '
k = consume (5, "fubar: [foo, bar]\n")'
- it recognizes a sequence start token:
expect (k ().type).to_be "FLOW_SEQUENCE_START"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 7, index = 7}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 8, index = 8}
- describe FLOW_SEQUENCE_END:
- before: '
k = consume (9, "fubar: [foo, bar]\n")'
- it recognizes a sequence end token:
expect (k ().type).to_equal "FLOW_SEQUENCE_END"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 16, index = 16}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 17, index = 17}
- describe FLOW_ENTRY:
- before: 'k = consume (6, "{foo: bar, baz: quux}")'
- it recognizes a sequence flow entry: '
k = consume (6, "[foo: bar, baz: quux]")
expect (k ().type).to_be "FLOW_ENTRY"'
- it recognizes a mapping flow entry:
expect (k ().type).to_be "FLOW_ENTRY"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 9, index = 9}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 10, index = 10}
- describe FLOW_MAPPING_START:
- before: 'k = consume (1, "{foo: bar, baz: quux}")'
- it recognizes flow style:
expect (k ().type).to_be "FLOW_MAPPING_START"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 0, index = 0}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 1, index = 1}
- describe FLOW_MAPPING_END:
- before: 'k = consume (6, "{foo: bar}\n")'
- it recognizes the mapping end token:
expect (k ().type).to_equal "FLOW_MAPPING_END"
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 9, index = 9}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 10, index = 10}
- describe KEY:
- before: 'k = consume (2, "{the key: the value, another key: meh}")'
- it recognizes a flow mapping key token:
expect (k ().type).to_be "KEY"
- it recognizes a block mapping key token: '
k = consume (2, "the key: the value\nanother key: meh\n")
expect (k ().type).to_be "KEY"'
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 1, index = 1}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 1, index = 1}
- describe VALUE:
- before: 'k = consume (4, "{the key: the value, another key: meh}")'
- it recognizes a flow mapping value token:
expect (k ().type).to_be "VALUE"
- it recognizes a block mapping key value: '
k = consume (4, "the key: the value\nanother key: meh\n")
expect (k ().type).to_be "VALUE"'
- it reports token start marker:
expect (k ().start_mark).to_equal {line = 0, column = 8, index = 8}
- it reports token end marker:
expect (k ().end_mark).to_equal {line = 0, column = 9, index = 9}

View file

@ -0,0 +1,121 @@
# LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
# Copyright (C) 2013-2022 Gary V. Vaughan
before:
this_module = 'lyaml.functional'
global_table = '_G'
exported_apis = {'NULL', 'anyof', 'id', 'iscallable', 'isnull'}
M = require(this_module)
nop = function() end
fail = function() return nil end
pass = function() return false end
throw = function() error 'oh noes!' end
parmlist = pack(
nil,
false,
42,
'str',
io.stderr,
{},
nop,
setmetatable({}, {__call=nop})
)
specify functional:
- context when required:
- context by name:
- it does not touch the global table:
expect(show_apis{added_to=global_table, by=this_module}).to_equal{}
- it exports the decumented apis:
t = {}
for k in pairs(M) do t[#t + 1] = k end
expect(t).to_contain.a_permutation_of(exported_apis)
- describe anyof:
- before:
f = M.anyof
- it returns a callable:
expect(f{nop}).to_be_callable()
expect(f{nop, nop}).to_be_callable()
- it returns a lazy function that calls arguments if necessary:
expect(f{pass, throw}()).not_to_raise 'any error'
expect(f{pass, throw}()).not_to_be(nil)
- it silently skips non-callable arguments:
expect(f(list({nil, false, true}))()).to_be(nil)
expect(f{1, 2, pass, 'pass'}()).not_to_be(nil)
- it returns non-nil if any callable returns non-nil:
expect(f{pass, pass, fail}()).not_to_be(nil)
expect(f{pass, fail}()).not_to_be(nil)
expect(f{fail, pass}()).not_to_be(nil)
- it returns nil if all callables are nil:
expect(f{fail}()).to_be(nil)
expect(f{fail, fail}()).to_be(nil)
expect(f{fail, fail, fail}()).to_be(nil)
- it propagates data to all callables:
expect(f{fail, function(...) return select('#', ...) end}(nil)).to_be(1)
expect(f{function(...) return select('#', ...) end, fail}(nil, false)).to_be(2)
expect(f{function(...) return select('#', ...) end, pass}(nil, false)).to_be(2)
- it returns the first non-nil callables result:
expect(f{fail, function(...) return ... end}(42)).to_be(42)
expect(f{function(...) return ... end, fail}(42)).to_be(42)
expect(f{pass, fail}(42)).to_be(false)
expect(f{fail, pass}(42)).to_be(false)
- it propagates only the first return value:
expect(f{fail, function(...) return ... end}(1, 2, 5)).to_be(1)
expect(f{function(...) return ... end, fail}(1, 2, 5)).to_be(1)
expect(f{function(...) return ... end, pass}(1, 2, 5)).to_be(1)
- describe id:
- before:
f = M.id
- it returns its own argument:
expect(f(false)).to_be(false)
expect(f(42)).to_be(42)
- it handles nil argumen:
expect(f(nil)).to_be(nil)
- it handles missing argument:
expect(f()).to_be()
- it returns multiple arguments:
expect(f(nil, 1, fn, false, nil)).to_be(nil, 1, fn, false, nil)
- describe iscallable:
- before:
f = M.iscallable
- it returns callable for a callable:
expect(f(f)).to_be(f)
expect(f(setmetatable({}, {__call=f}))).to_be(f)
- it returns nil for a non-callable:
expect(f()).to_be(nil)
expect(f(nil)).to_be(nil)
expect(f(false)).to_be(nil)
expect(f(true)).to_be(nil)
expect(f'str').to_be(nil)
expect(f(42)).to_be(nil)
expect(f(setmetatable({}, {__index={}}))).to_be(nil)
expect(f(setmetatable({}, {__call=42}))).to_be(nil)
- describe isnull:
- before:
NULL = M.NULL
f = M.isnull
- it returns 'true' for a NULL argument:
expect(f(NULL)).to_be(true)
- it returns 'false' for any argument other than NULL:
for i=1,parmlist.n do
expect(f(parmlist[i])).to_be(false)
end

343
spec/lib_lyaml_spec.yaml Normal file
View file

@ -0,0 +1,343 @@
# LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
# Copyright (C) 2013-2022 Gary V. Vaughan
before: |
lyaml = require "lyaml"
-- Always use the new multi-doc capable API.
lyaml.legacy = lyaml.load
lyaml.load = function (stream) return lyaml.legacy (stream, true) end
specify lyaml:
- describe dumping:
- context streams:
- it writes an empty stream:
expect (lyaml.dump {}).to_equal ""
- context documents:
- it writes an empty document:
expect (lyaml.dump {""}).to_match "^%-%-%-%s*''\n%.%.%.%s*$"
- it writes consecutive documents:
expect (lyaml.dump {"one", "two"}).
to_match "^%-%-%-%s+one%s*\n%.%.%.%s*\n%-%-%-%s+two%s*\n%.%.%.%s*$"
- context scalars:
- it writes null:
expect (lyaml.dump {lyaml.null}).to_be "--- ~\n...\n"
expect (lyaml.dump {"~"}).to_be "--- '~'\n...\n"
- it writes booleans:
expect (lyaml.dump {"true"}).to_be "--- 'true'\n...\n"
expect (lyaml.dump {"yes"}).to_be "--- 'yes'\n...\n"
expect (lyaml.dump {"false"}).to_be "--- 'false'\n...\n"
expect (lyaml.dump {"no"}).to_be "--- 'no'\n...\n"
expect (lyaml.dump {true}).to_be "--- true\n...\n"
expect (lyaml.dump {false}).to_be "--- false\n...\n"
- it writes numbers:
expect (lyaml.dump {"123"}).to_be "--- '123'\n...\n"
expect (lyaml.dump {"12.3"}).to_be "--- '12.3'\n...\n"
expect (lyaml.dump {"0/0"}).to_be "--- 0/0\n...\n"
expect (lyaml.dump {123}).to_be "--- 123\n...\n"
expect (lyaml.dump {12.3}).to_be "--- 12.3\n...\n"
expect (lyaml.dump {0/0}).to_be "--- .nan\n...\n"
expect (lyaml.dump {math.huge}).to_be "--- .inf\n...\n"
expect (lyaml.dump {-math.huge}).to_be "--- -.inf\n...\n"
- it writes strings:
expect (lyaml.dump {"a string"}).to_be "--- a string\n...\n"
expect (lyaml.dump {"'a string'"}).to_be "--- '''a string'''\n...\n"
expect (lyaml.dump {"a\nmultiline\nstring"}).to_be "--- |-\n a\n multiline\n string\n...\n"
expect (lyaml.dump {""}).to_be "--- ''\n...\n"
- context sequences:
- it writes a sequence:
expect (lyaml.dump {{1, 2, 3}}).to_contain "- 1\n- 2\n- 3"
- context mappings:
- it writes a mapping: |
expect (lyaml.dump {{a=1, b=2, c=3, d=""}}).
to_contain.all_of {"a: 1", "b: 2", "c: 3", "d: ''"}
- it writes a mapping of mixed keys: |
expect (lyaml.dump {{[1]=1, [2]=2, three="three", four="4", [5]="five"}}).
to_contain.all_of {"1: 1", "2: 2", "three: three", "four: '4'", "5: five"}
- it writes a mapping of integer keys starting at two: |
expect (lyaml.dump {{[2]=2, [3]=3, [4]=4}}).
to_contain.all_of {"2: 2", "3: 3", "4: 4"}
- it writes a mapping of mixed keys starting at one: |
expect (lyaml.dump {{[1]=1, [2]=2, [3]=3, foo="bar"}}).
to_contain.all_of {"1: 1", "2: 2", "3: 3", "foo: bar"}
- it writes a mapping of mixed keys starting at two: |
expect (lyaml.dump {{[2]=2, [3]=3, [4]=4, foo="bar"}}).
to_contain.all_of {"2: 2", "3: 3", "4: 4", "foo: bar"}
- it writes a table containing nils (jumps in index) as mapping: |
expect (lyaml.dump {{1, 2, nil, 3, 4}}).
to_contain.all_of {"1: 1", "2: 2", "4: 3", "5: 4"}
- context anchors and aliases:
- before:
anchors = {
MAP = {["Mark McGwire"] = 65, ["Sammy Sosa"] = 63},
SEQ = {"Mark McGwire", "Sammy Sosa"},
}
- it writes scalar anchors: '
anchors = { SS = "Sammy Sosa" }
expect (lyaml.dump ({{{anchor = anchors.SS}, {alias = anchors.SS}}}, anchors)).
to_contain "- anchor: &SS Sammy Sosa\n- alias: *SS\n"'
- it writes sequence anchors: '
expect (lyaml.dump ({{{anchor = anchors.SEQ}, {alias = anchors.SEQ}}}, anchors)).
to_contain "\n- anchor: &SEQ\n - Mark McGwire\n - Sammy Sosa\n- alias: *SEQ\n"'
- it writes mapping anchors: '
expect (lyaml.dump ({{{anchor = anchors.MAP}, {alias = anchors.MAP}}}, anchors)).
to_match "\n%- anchor: &MAP\n %w+ %w+: %d+\n %w+ %w+: %d+\n%- alias: %*MAP\n"'
- describe loading:
- before:
fn = lyaml.load
- it loads an empty stream:
expect (fn "").to_equal {}
- it ignores comments: '
expect (fn "# A comment\nnon-comment # trailing comment\n").
to_equal { "non-comment" }'
- it diagnoses unexpected events: '
expect (fn "...").to_error "1:1: did not find expected node content"
expect (fn "---\n...\ngarbage\n").
to_error "2:1: did not find expected <document start>"
expect (fn " *ALIAS").
to_error "1:2: invalid reference: ALIAS"'
- context documents:
- it lyaml.loads an empty document:
expect (fn "---").to_equal {lyaml.null}
expect (fn "---\n").to_equal {lyaml.null}
expect (fn "---\n...").to_equal {lyaml.null}
expect (fn "---\n...\n").to_equal {lyaml.null}
- it lyaml.loads multiple documents:
expect (fn "one\n---\ntwo").to_equal {"one", "two"}
expect (fn "---\none\n---\ntwo").to_equal {"one", "two"}
expect (fn "one\n...\n---\ntwo\n...").to_equal {"one", "two"}
expect (fn "---\none\n...\n---\ntwo\n...").to_equal {"one", "two"}
- it reports an empty document:
expect (fn "---\n---\ntwo\n---").
to_equal {lyaml.null, "two", lyaml.null}
expect (fn "---\n...\n---\ntwo\n---").
to_equal {lyaml.null, "two", lyaml.null}
expect (fn "---\n...\n---\ntwo\n...\n---").
to_equal {lyaml.null, "two", lyaml.null}
expect (fn "---\n...\n---\ntwo\n...\n---\n...").
to_equal {lyaml.null, "two", lyaml.null}
- context version directive:
- it recognizes version number:
expect (fn "%YAML 1.1\n---").to_equal {lyaml.null}
- it diagneses missing document start:
expect (fn "%YAML 1.1").
to_error "expected <document start>"
- it diagnoses unsupported version:
expect (fn "%YAML 2.0\n---").
to_error "incompatible YAML document"
- context tag directive:
- it recognizes primary tag directive: '
expect (fn ("%TAG ! tag:yaml.org,2002:\n" ..
"---\n" ..
"!bool N")).to_equal {false}'
- it recognizes secondary tag directive: '
expect (fn ("%TAG !! tag:ben-kiki.org,2000:\n" ..
"---\n" ..
"!!bool untrue")).to_equal {"untrue"}'
- it recognizes named tag directive: '
expect (fn ("%TAG !bkk! tag:ben-kiki.org,2000:\n" ..
"---\n" ..
"!bkk!bool untrue")).to_equal {"untrue"}'
- it diagnoses undefined tag handles: '
expect (fn ("!bkk!bool untrue")).
to_error "undefined tag handle"'
- context scalars:
- it recognizes null: '
expect (fn "~").to_equal {lyaml.null}
expect (fn "foo: ").to_equal {{foo = lyaml.null}}
expect (fn "foo: ~").to_equal {{foo = lyaml.null}}
expect (fn "foo: !!null").to_equal {{foo = lyaml.null}}
expect (fn "foo: null").to_equal {{foo = lyaml.null}}
expect (fn "foo: Null").to_equal {{foo = lyaml.null}}
expect (fn "foo: NULL").to_equal {{foo = lyaml.null}}'
- it recognizes booleans: '
expect (fn "true").to_equal {true}
expect (fn "false").to_equal {false}
expect (fn "yes").to_equal {true}
expect (fn "no").to_equal {false}'
- it loads bare y and n as strings:
expect (fn "y").to_equal {"y"}
expect (fn "n").to_equal {"n"}
- it recognizes integers:
expect (fn "0b001010011010").to_equal {666}
expect (fn "0b0010_1001_1010").to_equal {666}
expect (fn "+0b001_010_011_010").to_equal {666}
expect (fn "-0b0010_1001_1010").to_equal {-666}
expect (fn "0_1232").to_equal {666}
expect (fn "-01232").to_equal {-666}
expect (fn "666").to_equal {666}
expect (fn "0x29a").to_equal {666}
expect (fn "-0x29a").to_equal {-666}
expect (fn "12_345_678").to_equal {12345678}
expect (fn "11:6").to_equal {666}
- it recognizes floats:
expect (fn "12.3").to_equal {12.3}
expect (fn "685.230_15e+03").to_equal {685230.15}
expect (fn "685_230.15e+03").to_equal {685230150.0}
expect (fn "12_345_678.9").to_equal {12345678.9}
expect (fn "11:6.777").to_equal {666.777}
expect (fn ".Inf").to_equal {math.huge}
expect (fn "-.inf").to_equal {-math.huge}
nant = fn ".NaN"
expect (nant[1]).not_to_equal (nant[1])
- it recognizes strings:
expect (fn "a string").to_equal {"a string"}
expect (fn "'''a string'''").to_equal {"'a string'"}
expect (fn "|-\n a\n multiline\n string").to_equal {"a\nmultiline\nstring"}
expect (fn "'yes'").to_equal {"yes"}
expect (fn "''").to_equal {""}
expect (fn '""').to_equal {""}
- context global tags:
- it recognizes !!null:
expect (fn "!!null").to_equal {lyaml.null}
- it recognizes !!bool: |
expect (fn '!!bool "true"').to_equal {true}
expect (fn '!!bool true').to_equal {true}
expect (fn '!!bool True').to_equal {true}
expect (fn '!!bool TRUE').to_equal {true}
expect (fn "!!bool 'false'").to_equal {false}
expect (fn '!!bool false').to_equal {false}
expect (fn '!!bool False').to_equal {false}
expect (fn '!!bool FALSE').to_equal {false}
expect (fn '!!bool "yes"').to_equal {true}
expect (fn "!!bool 'Yes'").to_equal {true}
expect (fn '!!bool YES').to_equal {true}
expect (fn '!!bool no').to_equal {false}
expect (fn "!!bool 'No'").to_equal {false}
expect (fn '!!bool "NO"').to_equal {false}
expect (fn '!!bool garbage').
to_raise "invalid 'tag:yaml.org,2002:bool' value: 'garbage'"
- it loads explicit y and n as booleans:
expect (fn '!!bool Y').to_equal {true}
expect (fn '!!bool y').to_equal {true}
expect (fn '!!bool N').to_equal {false}
expect (fn '!!bool n').to_equal {false}
- it recognizes !!float: |
expect (fn '!!float 42').to_equal {42.0}
expect (fn '!!float "42"').to_equal {42.0}
expect (fn '!!float +42').to_equal {42.0}
expect (fn '!!float 12.3').to_equal {12.3}
expect (fn '!!float -3.141592').to_equal {-3.141592}
expect (fn '!!float 685_230.15e+03').to_equal {685230150.0}
expect (fn '!!float +685.230_15e+03').to_equal {685230.15}
expect (fn '!!float 12_345_678.9').to_equal {12345678.9}
expect (fn '!!float -0:3:11:6.777').to_equal {-11466.777}
expect (fn '!!float .Inf').to_equal {math.huge}
expect (fn '!!float -.inf').to_equal {-math.huge}
nant = fn '!!float .NaN'
expect (nant[1]).not_to_equal (nant[1])
expect (fn '!!float garbage').
to_raise "invalid 'tag:yaml.org,2002:float' value: 'garbage'"
- it recognizes !!int: |
expect (fn '!!int 0b0010_1001_1010').to_equal {666}
expect (fn '!!int "+0b001_010_011_010"').to_equal {666}
expect (fn '!!int -0b0010_1001_1010').to_equal {-666}
expect (fn '!!int 0_1232').to_equal {666}
expect (fn '!!int "-01232"').to_equal {-666}
expect (fn '!!int 666').to_equal {666}
expect (fn '!!int 0668').to_equal {668}
expect (fn '!!int "0x29a"').to_equal {666}
expect (fn '!!int -0x29a').to_equal {-666}
expect (fn '!!int 12_345_678').to_equal {12345678}
expect (fn '!!int 11:6').to_equal {666}
expect (fn '!!int 12.3').
to_raise "invalid 'tag:yaml.org,2002:int' value: '12.3'"
expect (fn '!!int garbage').
to_raise "invalid 'tag:yaml.org,2002:int' value: 'garbage'"
- context sequences:
- it recognizes block sequences:
expect (fn "- ~\n- \n- true\n- 42").
to_equal {{lyaml.null, lyaml.null, true, 42}}
- it recognizes flow sequences:
expect (fn "[~, true, 42]").
to_equal {{lyaml.null, true, 42}}
- context anchors and aliases:
- it resolves scalar anchors: '
expect (fn "anchor: &SS Sammy Sosa\nalias: *SS").
to_equal {{anchor = "Sammy Sosa", alias = "Sammy Sosa"}}'
- it resolves sequence anchors: '
expect (fn "anchor: &SEQ [Mark McGwire, Sammy Sosa]\nalias: *SEQ").
to_equal {{anchor = {"Mark McGwire", "Sammy Sosa"},
alias = {"Mark McGwire", "Sammy Sosa"}}}'
- it resolves mapping anchors: '
expect (fn "anchor: &MAP {Mark McGwire: 65, Sammy Sosa: 63}\nalias: *MAP").
to_equal {{anchor = {["Mark McGwire"] = 65, ["Sammy Sosa"] = 63},
alias = {["Mark McGwire"] = 65, ["Sammy Sosa"] = 63}}}'
- context a map:
- it recognizes block mapping: |
expect (fn "'null': ~\nboolean: yes\nnumber: 3.14").
to_equal {{null = lyaml.null, boolean = true, number = 3.14}}
- it recognizes flow mapping: |
expect (fn "{null: null, boolean: yes, number: 3.14}").
to_equal {{[lyaml.null] = lyaml.null, boolean = true, number = 3.14}}
- context with merge keys:
- before: |
merge = {x=1, y=2}
override = {x=0, z=2}
bogus = true
YAML = "- &MERGE {x: 1, y: 2}\n" ..
"- &OVERRIDE {x: 0, z: 2}\n" ..
"- &BOGUS true\n"
- it diagnoses invalid merge events: |
expect (fn "-\n !!merge : x\n z: 3").
to_raise "invalid 'tag:yaml.org,2002:merge' merge event: x"
expect (fn "-\n << : x\n z: 3").
to_raise "invalid '<<' merge event: x"
- it diagnoses invalid merge alias types: |
expect (fn (YAML .. "-\n !!merge : *BOGUS")).
to_raise "invalid 'tag:yaml.org,2002:merge' merge event: true"
expect (fn (YAML .. "-\n << : *BOGUS")).
to_raise "invalid '<<' merge event: true"
- it diagnoses invalid merge sequence elements: |
expect (fn (YAML .. '-\n !!merge : [*MERGE, OVERRIDE]')).
to_raise "invalid 'tag:yaml.org,2002:merge' sequence element 2: OVERRIDE"
expect (fn (YAML .. '-\n <<: [*MERGE, OVERRIDE]')).
to_raise "invalid '<<' sequence element 2: OVERRIDE"
- it diagnoses invalid merge sequence alias tyes: |
expect (fn (YAML .. '-\n !!merge : [*MERGE, *BOGUS]')).
to_raise "invalid 'tag:yaml.org,2002:merge' sequence element 2: true"
expect (fn (YAML .. '-\n <<: [*MERGE, *BOGUS]')).
to_raise "invalid '<<' sequence element 2: true"
- it supports merging bare maps: |
expect (fn ("-\n !!merge : {x: 1, y: 2}\n z: 3")).
to_equal {{{x=1, y=2, z=3}}}
expect (fn "-\n <<: {x: 1, y: 2}\n z: 3").
to_equal {{{x=1, y=2, z=3}}}
- it supports merging map aliases: |
expect (fn (YAML .. "-\n !!merge : *MERGE\n z: 3")).
to_equal {{merge, override, bogus, {x=1, y=2, z=3}}}
expect (fn (YAML .. "-\n <<: *MERGE\n z: 3")).
to_equal {{merge, override, bogus, {x=1, y=2, z=3}}}
- it merges sequence of bare maps with decreasing precedence: |
expect (fn "-\n !!merge : [{x: 1, y: 2}, {x: 0, z: 2}]\n z: 3").
to_equal {{{x=1, y=2, z=3}}}
expect (fn "-\n <<: [{x: 1, y: 2}, {x: 0, z: 2}]\n z: 3").
to_equal {{{x=1, y=2, z=3}}}
- it merges sequence of aliases with decreasing precedence: |
expect (fn (YAML .. "-\n !!merge : [*MERGE, *OVERRIDE]\n z: 3")).
to_equal {{merge, override, bogus, {x=1, y=2, z=3}}}
expect (fn (YAML .. "-\n <<: [*MERGE, *OVERRIDE]\n z: 3")).
to_equal {{merge, override, bogus, {x=1, y=2, z=3}}}
- it merges a sequence alias with decreasing precedence: |
seq = {merge, override}
r = {{merge, override, bogus, seq, {x=1, y=2, z=3}}}
expect (fn (YAML .. "- &SEQ [*MERGE, *OVERRIDE]\n" ..
"-\n !!merge : *SEQ\n z: 3")).to_equal (r)
expect (fn (YAML .. "- &SEQ [*MERGE, *OVERRIDE]\n" ..
"-\n <<: *SEQ\n z: 3")).to_equal (r)

277
spec/spec_helper.lua Normal file
View file

@ -0,0 +1,277 @@
--[[
LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
Copyright (C) 2013-2022 Gary V. Vaughan
]]
do
local std = require 'specl.std'
local spawn = require 'specl.shell'.spawn
local objdir = spawn('./build-aux/luke --value=objdir').output
package.path = std.package.normalize(
'./lib/?.lua',
'./lib/?/init.lua',
package.path
)
package.cpath = std.package.normalize(
'./' .. objdir:match("^objdir='(.*)'") .. '/?.so',
'./' .. objdir:match("^objdir='(.*)'") .. '/?.dll',
package.cpath
)
end
local hell = require 'specl.shell'
yaml = require 'yaml'
BOM = string.char(254, 255) -- UTF-16 Byte Order Mark
-- Allow use of bare 'pack' and 'unpack' even in Lua > 5.2.
pack = table.pack or function(...) return {n = select('#', ...), ...} end
unpack = table.unpack or unpack
list = pack
function dump(e)
print(std.string.prettytostring(e))
end
function github_issue(n)
return 'see http://github.com/gvvaughan/lyaml/issues/' .. tostring(n)
end
-- Output a list of event tables to the given emitter.
function emitevents(emitter, list)
for _, v in ipairs(list) do
if type(v) == 'string' then
ok, msg = emitter.emit {type=v}
elseif type(v) == 'table' then
ok, msg = emitter.emit(v)
else
error 'expected table or string argument'
end
if not ok then
error(msg)
elseif ok and msg then
return msg
end
end
end
-- Create a new emitter and send STREAM_START, listed events and STREAM_END.
function emit(list)
local emitter = yaml.emitter()
emitter.emit {type='STREAM_START'}
emitevents(emitter, list)
local _, msg = emitter.emit {type='STREAM_END'}
return msg
end
-- Create a new parser for STR, and consume the first N events.
function consume(n, str)
local e = yaml.parser(str)
for n = 1, n do
e()
end
return e
end
-- Return a new table with only elements of T that have keys listed
-- in the following arguments.
function filter(t, ...)
local u = {}
for _, k in ipairs {...} do
u[k] = t[k]
end
return u
end
function iscallable(x)
return type(x) == 'function' or type((getmetatable(x) or {}).__call) == 'function'
end
local function mkscript(code)
local f = os.tmpname()
local h = io.open(f, 'w')
-- TODO: Move this into specl, or expose arguments so that we can
-- turn this on and off based on specl `--coverage` arg.
h:write "pcall(require, 'luacov')"
h:write(code)
h:close()
return f
end
-- Allow user override of LUA binary used by hell.spawn, falling
-- back to environment PATH search for 'lua' if nothing else works.
local LUA = os.getenv 'LUA' or 'lua'
--- Run some Lua code with the given arguments and input.
-- @string code valid Lua code
-- @tparam[opt={}] string|table arg single argument, or table of
-- arguments for the script invocation.
-- @string[opt] stdin standard input contents for the script process
-- @treturn specl.shell.Process|nil status of resulting process if
-- execution was successful, otherwise nil
function luaproc(code, arg, stdin)
local f = mkscript(code)
if type(arg) ~= 'table' then arg = {arg} end
local cmd = {LUA, f, unpack(arg)}
-- inject env and stdin keys separately to avoid truncating `...` in
-- cmd constructor
cmd.env = { LUA_PATH=package.path, LUA_INIT='', LUA_INIT_5_2='' }
cmd.stdin = stdin
local proc = hell.spawn(cmd)
os.remove(f)
return proc
end
local function tabulate_output(code)
local proc = luaproc(code)
if proc.status ~= 0 then return error(proc.errout) end
local r = {}
proc.output:gsub('(%S*)[%s]*',
function(x)
if x ~= '' then r[x] = true end
end)
return r
end
--- Show changes to tables wrought by a require statement.
-- There are a few modes to this function, controlled by what named
-- arguments are given. Lists new keys in T1 after `require "import"`:
--
-- show_apis {added_to=T1, by=import}
--
-- @tparam table argt one of the combinations above
-- @treturn table a list of keys according to criteria above
function show_apis(argt)
return tabulate_output([[
local before, after = {}, {}
for k in pairs(]] .. argt.added_to .. [[) do
before[k] = true
end
local M = require ']] .. argt.by .. [['
for k in pairs(]] .. argt.added_to .. [[) do
after[k] = true
end
for k in pairs(after) do
if not before[k] then print(k) end
end
]])
end
--[[ ========= ]]--
--[[ Call Spy. ]]--
--[[ ========= ]]--
spy = function(fn)
return setmetatable({}, {
__call = function(self, ...)
self[#self + 1] = list(...)
return fn(...)
end,
})
end
do
--[[ ================ ]]--
--[[ Custom matchers. ]]--
--[[ ================ ]]--
local matchers = require 'specl.matchers'
local eqv = require 'specl.std'.operator.eqv
local str = require 'specl.std'.string.tostring
local Matcher, matchers = matchers.Matcher, matchers.matchers
local concat = table.concat
matchers.be_called_with = Matcher {
function(self, actual, expected)
for i,v in ipairs(expected or {}) do
if not eqv(actual[i], v) then
return false
end
end
return true
end,
actual = 'argmuents',
format_expect = function(self, expect)
return ' arguments (' .. str(expect) .. '), '
end,
}
matchers.be_callable = Matcher {
function(self, actual, _)
return iscallable(actual)
end,
actual = 'callable',
format_expect = function(self, expect)
return ' callable, '
end,
}
matchers.be_falsey = Matcher {
function(self, actual, _)
return not actual and true or false
end,
actual = 'falsey',
format_expect = function(self, expect)
return ' falsey, '
end,
}
matchers.be_truthy = Matcher {
function(self, actual, _)
return actual and true or false
end,
actual = 'truthy',
format_expect = function(self, expect)
return ' truthy, '
end,
}
matchers.have_type = Matcher {
function(self, actual, expected)
return type(actual) == expected or (getmetatable(actual) or {})._type == expected
end,
actual = 'type',
format_expect = function(self, expect)
local article = 'a'
if match(expect, '^[aehiou]') then
article = 'an'
end
return concat{' ', article, ' ', expect, ', '}
end
}
end