diff options
author | Baptiste Daroussin <bapt@FreeBSD.org> | 2025-06-26 07:09:58 +0000 |
---|---|---|
committer | Baptiste Daroussin <bapt@FreeBSD.org> | 2025-06-26 07:09:58 +0000 |
commit | 95286bbb7907e5ae9f7971e09072d1805f50cd9b (patch) | |
tree | 1375c664480c303c38f138fa8fb2725a0393d4f7 |
31 files changed, 7317 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..19d7cbb6dd7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*~ +*.o +*.so +*.src.rock +/ChangeLog +/build-aux/config.ld +/luacov.*.out +/lyaml-*.tar.gz +/TAGS diff --git a/.luacov b/.luacov new file mode 100644 index 000000000000..0aa52d25e0ea --- /dev/null +++ b/.luacov @@ -0,0 +1,8 @@ +modules = { + ['lyaml'] = 'lib/lyaml/init.lua', + ['lyaml.*'] = 'lib', +} + +runreport = true + +tick = true diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000000..1eb673371bf7 --- /dev/null +++ b/AUTHORS @@ -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> diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..402e64dfec29 --- /dev/null +++ b/LICENSE @@ -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. +==================================================================== diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 000000000000..32611d83671a --- /dev/null +++ b/NEWS.md @@ -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! diff --git a/README.md b/README.md new file mode 100644 index 000000000000..d1e96845ddfa --- /dev/null +++ b/README.md @@ -0,0 +1,232 @@ +LYAML +===== + +Copyright (C) 2013-2022 Gary V. Vaughan + +[](https://mit-license.org) +[](https://github.com/gvvaughan/lyaml/actions) +[](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/ diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in new file mode 100644 index 000000000000..0d55b9e94c30 --- /dev/null +++ b/build-aux/config.ld.in @@ -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 diff --git a/build-aux/luke b/build-aux/luke new file mode 100755 index 000000000000..3d61f784cbca --- /dev/null +++ b/build-aux/luke @@ -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)) diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 000000000000..bf7a957f51bd --- /dev/null +++ b/doc/index.html @@ -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> diff --git a/doc/ldoc.css b/doc/ldoc.css new file mode 100644 index 000000000000..52c4ad2bd8a2 --- /dev/null +++ b/doc/ldoc.css @@ -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; } + diff --git a/doc/modules/lyaml.explicit.html b/doc/modules/lyaml.explicit.html new file mode 100644 index 000000000000..2667c4e006b2 --- /dev/null +++ b/doc/modules/lyaml.explicit.html @@ -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> diff --git a/doc/modules/lyaml.functional.html b/doc/modules/lyaml.functional.html new file mode 100644 index 000000000000..b003a961ff28 --- /dev/null +++ b/doc/modules/lyaml.functional.html @@ -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> diff --git a/doc/modules/lyaml.html b/doc/modules/lyaml.html new file mode 100644 index 000000000000..2deb6a4b6be7 --- /dev/null +++ b/doc/modules/lyaml.html @@ -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> diff --git a/doc/modules/lyaml.implicit.html b/doc/modules/lyaml.implicit.html new file mode 100644 index 000000000000..935579f705c6 --- /dev/null +++ b/doc/modules/lyaml.implicit.html @@ -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> diff --git a/ext/yaml/emitter.c b/ext/yaml/emitter.c new file mode 100644 index 000000000000..b71fd14f4c1d --- /dev/null +++ b/ext/yaml/emitter.c @@ -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; +} diff --git a/ext/yaml/lyaml.h b/ext/yaml/lyaml.h new file mode 100644 index 000000000000..9892a48ea4c4 --- /dev/null +++ b/ext/yaml/lyaml.h @@ -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 diff --git a/ext/yaml/parser.c b/ext/yaml/parser.c new file mode 100644 index 000000000000..3136abd49045 --- /dev/null +++ b/ext/yaml/parser.c @@ -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; +} diff --git a/ext/yaml/scanner.c b/ext/yaml/scanner.c new file mode 100644 index 000000000000..6d7276517f42 --- /dev/null +++ b/ext/yaml/scanner.c @@ -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; +} diff --git a/ext/yaml/yaml.c b/ext/yaml/yaml.c new file mode 100644 index 000000000000..54478610134f --- /dev/null +++ b/ext/yaml/yaml.c @@ -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; +} diff --git a/lib/lyaml/explicit.lua b/lib/lyaml/explicit.lua new file mode 100644 index 000000000000..98a38331ead6 --- /dev/null +++ b/lib/lyaml/explicit.lua @@ -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, +} diff --git a/lib/lyaml/functional.lua b/lib/lyaml/functional.lua new file mode 100644 index 000000000000..556e9489505a --- /dev/null +++ b/lib/lyaml/functional.lua @@ -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, +} diff --git a/lib/lyaml/implicit.lua b/lib/lyaml/implicit.lua new file mode 100644 index 000000000000..fe58025b560d --- /dev/null +++ b/lib/lyaml/implicit.lua @@ -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, +} diff --git a/lib/lyaml/init.lua b/lib/lyaml/init.lua new file mode 100644 index 000000000000..95e4036ea7c9 --- /dev/null +++ b/lib/lyaml/init.lua @@ -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, +} diff --git a/lukefile b/lukefile new file mode 100644 index 000000000000..318281fd78e7 --- /dev/null +++ b/lukefile @@ -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', +} diff --git a/lyaml-6.2.8-1.rockspec b/lyaml-6.2.8-1.rockspec new file mode 100644 index 000000000000..dce9e45a56cf --- /dev/null +++ b/lyaml-6.2.8-1.rockspec @@ -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 + diff --git a/spec/ext_yaml_emitter_spec.yaml b/spec/ext_yaml_emitter_spec.yaml new file mode 100644 index 000000000000..385d58f7de3c --- /dev/null +++ b/spec/ext_yaml_emitter_spec.yaml @@ -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"} diff --git a/spec/ext_yaml_parser_spec.yaml b/spec/ext_yaml_parser_spec.yaml new file mode 100644 index 000000000000..2438c42188e0 --- /dev/null +++ b/spec/ext_yaml_parser_spec.yaml @@ -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} diff --git a/spec/ext_yaml_scanner_spec.yaml b/spec/ext_yaml_scanner_spec.yaml new file mode 100644 index 000000000000..4d8e633d2cc2 --- /dev/null +++ b/spec/ext_yaml_scanner_spec.yaml @@ -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} diff --git a/spec/lib_lyaml_functional_spec.yaml b/spec/lib_lyaml_functional_spec.yaml new file mode 100644 index 000000000000..cfd8676f0176 --- /dev/null +++ b/spec/lib_lyaml_functional_spec.yaml @@ -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 + diff --git a/spec/lib_lyaml_spec.yaml b/spec/lib_lyaml_spec.yaml new file mode 100644 index 000000000000..f6c7e1ef4bed --- /dev/null +++ b/spec/lib_lyaml_spec.yaml @@ -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) diff --git a/spec/spec_helper.lua b/spec/spec_helper.lua new file mode 100644 index 000000000000..1ba7a3a7559f --- /dev/null +++ b/spec/spec_helper.lua @@ -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 |