diff --git a/files/clibs/iup.dll b/files/clibs/iup.dll index 4695b31..3d28890 100644 Binary files a/files/clibs/iup.dll and b/files/clibs/iup.dll differ diff --git a/files/clibs/iup_mglplot.dll b/files/clibs/iup_mglplot.dll new file mode 100644 index 0000000..ed6edb5 Binary files /dev/null and b/files/clibs/iup_mglplot.dll differ diff --git a/files/clibs/iup_pplot.dll b/files/clibs/iup_pplot.dll index a4017ce..94e48aa 100644 Binary files a/files/clibs/iup_pplot.dll and b/files/clibs/iup_pplot.dll differ diff --git a/files/clibs/iup_scintilla.dll b/files/clibs/iup_scintilla.dll new file mode 100644 index 0000000..93ab6ce Binary files /dev/null and b/files/clibs/iup_scintilla.dll differ diff --git a/files/clibs/iupcd.dll b/files/clibs/iupcd.dll index bc28c33..0ee23ec 100644 Binary files a/files/clibs/iupcd.dll and b/files/clibs/iupcd.dll differ diff --git a/files/clibs/iupcontrols.dll b/files/clibs/iupcontrols.dll index 3f53d57..ded419d 100644 Binary files a/files/clibs/iupcontrols.dll and b/files/clibs/iupcontrols.dll differ diff --git a/files/clibs/iupgl.dll b/files/clibs/iupgl.dll index e5696b7..e89fb66 100644 Binary files a/files/clibs/iupgl.dll and b/files/clibs/iupgl.dll differ diff --git a/files/clibs/iupgtk.dll b/files/clibs/iupgtk.dll index 14d6ffd..833bbf9 100644 Binary files a/files/clibs/iupgtk.dll and b/files/clibs/iupgtk.dll differ diff --git a/files/clibs/iupim.dll b/files/clibs/iupim.dll index 6d4e98f..5a2057d 100644 Binary files a/files/clibs/iupim.dll and b/files/clibs/iupim.dll differ diff --git a/files/clibs/iupimglib.dll b/files/clibs/iupimglib.dll index 4654c8b..4cf91d6 100644 Binary files a/files/clibs/iupimglib.dll and b/files/clibs/iupimglib.dll differ diff --git a/files/clibs/iuplua51.dll b/files/clibs/iuplua51.dll index 22ca3b9..8a7a66b 100644 Binary files a/files/clibs/iuplua51.dll and b/files/clibs/iuplua51.dll differ diff --git a/files/clibs/iuplua_mglplot51.dll b/files/clibs/iuplua_mglplot51.dll new file mode 100644 index 0000000..4591eae Binary files /dev/null and b/files/clibs/iuplua_mglplot51.dll differ diff --git a/files/clibs/iuplua_pplot51.dll b/files/clibs/iuplua_pplot51.dll index b391753..a2606de 100644 Binary files a/files/clibs/iuplua_pplot51.dll and b/files/clibs/iuplua_pplot51.dll differ diff --git a/files/clibs/iuplua_scintilla51.dll b/files/clibs/iuplua_scintilla51.dll new file mode 100644 index 0000000..44b3998 Binary files /dev/null and b/files/clibs/iuplua_scintilla51.dll differ diff --git a/files/clibs/iupluacd51.dll b/files/clibs/iupluacd51.dll index 9fe2735..9b25c7c 100644 Binary files a/files/clibs/iupluacd51.dll and b/files/clibs/iupluacd51.dll differ diff --git a/files/clibs/iupluacontrols51.dll b/files/clibs/iupluacontrols51.dll index 462b37b..fc46d8d 100644 Binary files a/files/clibs/iupluacontrols51.dll and b/files/clibs/iupluacontrols51.dll differ diff --git a/files/clibs/iupluagl51.dll b/files/clibs/iupluagl51.dll index 9d1959e..fe16be8 100644 Binary files a/files/clibs/iupluagl51.dll and b/files/clibs/iupluagl51.dll differ diff --git a/files/clibs/iupluaim51.dll b/files/clibs/iupluaim51.dll index be0833d..6c40922 100644 Binary files a/files/clibs/iupluaim51.dll and b/files/clibs/iupluaim51.dll differ diff --git a/files/clibs/iupluaimglib51.dll b/files/clibs/iupluaimglib51.dll index b9a4bf0..d2fca5e 100644 Binary files a/files/clibs/iupluaimglib51.dll and b/files/clibs/iupluaimglib51.dll differ diff --git a/files/clibs/iupluaole51.dll b/files/clibs/iupluaole51.dll index cda8aa8..9f17e0b 100644 Binary files a/files/clibs/iupluaole51.dll and b/files/clibs/iupluaole51.dll differ diff --git a/files/clibs/iupluatuio51.dll b/files/clibs/iupluatuio51.dll index fd7d2e3..46e1556 100644 Binary files a/files/clibs/iupluatuio51.dll and b/files/clibs/iupluatuio51.dll differ diff --git a/files/clibs/iupluaweb51.dll b/files/clibs/iupluaweb51.dll index 351275f..9b52548 100644 Binary files a/files/clibs/iupluaweb51.dll and b/files/clibs/iupluaweb51.dll differ diff --git a/files/clibs/iupole.dll b/files/clibs/iupole.dll index 8d59dea..54a7314 100644 Binary files a/files/clibs/iupole.dll and b/files/clibs/iupole.dll differ diff --git a/files/clibs/iuptuio.dll b/files/clibs/iuptuio.dll index 3e78083..f816cb9 100644 Binary files a/files/clibs/iuptuio.dll and b/files/clibs/iuptuio.dll differ diff --git a/files/clibs/iupweb.dll b/files/clibs/iupweb.dll index a89dd68..c0c5aa0 100644 Binary files a/files/clibs/iupweb.dll and b/files/clibs/iupweb.dll differ diff --git a/files/clibs/lpeg.dll b/files/clibs/lpeg.dll index c8b70fa..6ca7102 100755 Binary files a/files/clibs/lpeg.dll and b/files/clibs/lpeg.dll differ diff --git a/files/docs/iup/iup-3.5_Docs.chm b/files/docs/iup/iup-3.8_Docs.chm similarity index 51% rename from files/docs/iup/iup-3.5_Docs.chm rename to files/docs/iup/iup-3.8_Docs.chm index 7977792..795b620 100644 Binary files a/files/docs/iup/iup-3.5_Docs.chm and b/files/docs/iup/iup-3.8_Docs.chm differ diff --git a/files/docs/lpeg/lpeg.html b/files/docs/lpeg/lpeg.html index 42d29a4..b31d575 100755 --- a/files/docs/lpeg/lpeg.html +++ b/files/docs/lpeg/lpeg.html @@ -1,28 +1,27 @@ - + "//www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + LPeg - Parsing Expression Grammars For Lua -
LPeg
- Parsing Expression Grammars For Lua, version 0.9 + Parsing Expression Grammars For Lua, version 1.1
@@ -56,16 +55,16 @@

Introduction

LPeg is a new pattern-matching library for Lua, based on - + Parsing Expression Grammars (PEGs). This text is a reference manual for the library. For a more formal treatment of LPeg, as well as some discussion about its implementation, see - + A Text Pattern-Matching Tool based on Parsing Expression Grammars. (You may also be interested in my -talk about LPeg +talk about LPeg given at the III Lua Workshop.)

@@ -87,7 +86,7 @@

Introduction

On the other hand, first-class patterns allow much better documentation (as it is easy to comment the code, -to use auxiliary variables to break complex definitions, etc.) +to break complex definitions in smaller parts, etc.) and are extensible, as we can define new functions to create and compose patterns.

@@ -101,12 +100,15 @@

Introduction

OperatorDescription lpeg.P(string) Matches string literally -lpeg.P(number) - Matches exactly number characters +lpeg.P(n) + Matches exactly n characters lpeg.S(string) Matches any character in string (Set) lpeg.R("xy") Matches any character between x and y (Range) +lpeg.utfR(cp1, cp2) + Matches an UTF-8 code point between cp1 and + cp2 patt^n Matches at least n repetitions of patt patt^-n @@ -122,6 +124,9 @@

Introduction

Equivalent to ("" - patt) #patt Matches patt but consumes no input +lpeg.B(patt) + Matches patt behind the current position, + consuming no input

As a very simple example, @@ -129,18 +134,19 @@

Introduction

matches a non-empty sequence of digits. As a not so simple example, -lpeg.P(1) -(which can be written as lpeg.P(-1) +(which can be written as lpeg.P(-1), or simply -1 for operations expecting a pattern) matches an empty string only if it cannot match a single character; -so, it succeeds only at the subject's end. +so, it succeeds only at the end of the subject.

LPeg also offers the re module, which implements patterns following a regular-expression style (e.g., [09]+). -(This module is 200 lines of Lua code, -and of course uses LPeg to parse regular expressions.) +(This module is 270 lines of Lua code, +and of course it uses LPeg to parse regular expressions and +translate them to regular LPeg patterns.)

@@ -153,14 +159,14 @@

lpeg.match (pattern, subject [, init])captured values +or the captured values (if the pattern captured any value).

An optional numeric argument init makes the match -starts at that position in the subject string. -As usual in Lua libraries, +start at that position in the subject string. +As in the Lua standard libraries, a negative value counts from the end.

@@ -184,9 +190,23 @@

lpeg.type (value)

Otherwise returns nil.

-

lpeg.version ()

+

lpeg.version

-Returns a string with the running version of LPeg. +A string (not a function) with the running version of LPeg. +

+ +

lpeg.setmaxstack (max)

+

+Sets a limit for the size of the backtrack stack used by LPeg to +track calls and choices. +(The default limit is 400.) +Most well-written patterns need little backtrack levels and +therefore you seldom need to change this limit; +before changing it you should try to rewrite your +pattern to avoid the need for extra space. +Nevertheless, a few useful patterns may overflow. +Also, with recursive grammars, +subjects with deep recursion may also need larger limits.

@@ -216,7 +236,7 @@

lpeg.P (value)

  • If the argument is a string, -it is translated to a pattern that matches literally the string. +it is translated to a pattern that matches the string literally.

  • @@ -227,9 +247,10 @@

    lpeg.P (value)

  • If the argument is a negative number -n, the result is a pattern that -succeeds only if the input string does not have n characters: -It is equivalent to the unary minus operation -applied over the pattern corresponding to the (non-negative) value n. +succeeds only if the input string has less than n characters left: +lpeg.P(-n) +is equivalent to -lpeg.P(n) +(see the unary minus operation).

  • @@ -254,6 +275,22 @@

    lpeg.P (value)

    +

    lpeg.B(patt)

    +

    +Returns a pattern that +matches only if the input string at the current position +is preceded by patt. +Pattern patt must match only strings +with some fixed length, +and it cannot contain captures. +

    + +

    +Like the and predicate, +this pattern never consumes any input, +independently of success or failure. +

    +

    lpeg.R ({range})

    @@ -294,6 +331,15 @@

    lpeg.S (string)

    +

    lpeg.utfR (cp1, cp2)

    +

    +Returns a pattern that matches a valid UTF-8 byte sequence +representing a code point in the range [cp1, cp2]. +The range is limited by the natural Unicode limit of 0x10FFFF, +but may include surrogates. +

    + +

    lpeg.V (v)

    This operation creates a non-terminal (a variable) @@ -337,14 +383,15 @@

    #patt

    matches only if the input string matches patt, but without consuming any input, independently of success or failure. -(This pattern is equivalent to +(This pattern is called an and predicate +and it is equivalent to &patt in the original PEG notation.)

    +

    -When it succeeds, -#patt produces all captures produced by patt. -

    . +This pattern never produces any capture. +

    -patt

    @@ -397,11 +444,20 @@

    patt1 + patt2

    patt1 - patt2

    -Returns a pattern equivalent to !patt2 patt1. +Returns a pattern equivalent to !patt2 patt1 +in the origial PEG notation. This pattern asserts that the input does not match patt2 and then matches patt1.

    +

    +When successful, +this pattern produces all captures from patt1. +It never produces any capture from patt2 +(as either patt2 fails or +patt1 - patt2 fails). +

    +

    If both patt1 and patt2 are character sets, @@ -436,14 +492,14 @@

    patt^n

    If n is nonnegative, this pattern is -equivalent to pattn patt*. -It matches at least n occurrences of patt. +equivalent to pattn patt*: +It matches n or more occurrences of patt.

    Otherwise, when n is negative, -this pattern is equivalent to (patt?)-n. -That is, it matches at most -n +this pattern is equivalent to (patt?)-n: +It matches at most |n| occurrences of patt.

    @@ -520,13 +576,22 @@

    Grammars

    B = "b" * lpeg.V"S" + "a" * lpeg.V"B" * lpeg.V"B", } * -1 +

    +It is equivalent to the following grammar in standard PEG notation: +

    +
    +  S <- 'a' B / 'b' A / ''
    +  A <- 'a' S / 'b' A A
    +  B <- 'b' S / 'a' B B
    +

    Captures

    -A capture is a pattern that creates values -(the so called semantic information) when it matches. +A capture is a pattern that produces values +(the so called semantic information) +according to what it matches. LPeg offers several kinds of captures, which produces values based on matches and combine these values to produce new values. @@ -539,21 +604,22 @@

    Captures

    - + - + - - + + + optionally tagged with key @@ -564,22 +630,27 @@

    Captures

    + + + +
    OperationWhat it Produces
    lpeg.C(patt)the match for patt
    the match for patt plus all captures + made by patt
    lpeg.Carg(n) the value of the nth extra argument to lpeg.match (matches the empty string)
    lpeg.Cb(name)
    lpeg.Cb(key) the values produced by the previous - group capture named name + group capture named key (matches the empty string)
    lpeg.Cc(values) the given values (matches the empty string)
    lpeg.Cf(patt, func)a folding of the captures from patt
    lpeg.Cg(patt, [name])folding capture (deprecated)
    lpeg.Cg(patt [, key]) the values produced by patt, - optionally tagged with name
    lpeg.Cp() the current position (matches the empty string)
    lpeg.Cs(patt)
    patt / string string, with some marks replaced by captures of patt
    patt / numberthe n-th value captured by patt, +or no value when number is zero.
    patt / table table[c], where c is the (first) capture of patt
    patt / function the returns of function applied to the captures of patt
    patt % functionproduces no value; + it accummulates the captures from patt + into the previous capture through function +
    lpeg.Cmt(patt, function) the returns of function applied to the captures of patt; the application is done at match time

    -A capture pattern produces its values every time it succeeds. -For instance, -a capture inside a loop produces as many values as matched by the loop. -A capture produces a value only when it succeeds. +A capture pattern produces its values only when it succeeds. For instance, the pattern lpeg.C(lpeg.P"a"^-1) produces the empty string when there is no "a" @@ -587,21 +658,27 @@

    Captures

    while the pattern lpeg.C("a")^-1 does not produce any value when there is no "a" (because the pattern "a" fails). +A pattern inside a loop or inside a recursive structure +produces values for each match.

    Usually, -LPeg evaluates all captures only after (and if) the entire match succeeds. -At match time it only gathers enough information -to produce the capture values later. -As a particularly important consequence, -most captures cannot affect the way a pattern matches a subject. +LPeg does not specify when (and if) it evaluates its captures. +(As an example, +consider the pattern lpeg.P"a" / func / 0. +Because the "division" by 0 instructs LPeg to throw away the +results from the pattern, +it is not specified whether LPeg will call func.) +Therefore, captures should avoid side effects. +Moreover, +captures cannot affect the way a pattern matches a subject. The only exception to this rule is the so-called match-time capture. When a match-time capture matches, it forces the immediate evaluation of all its nested captures and then calls its corresponding function, -which tells whether the match succeeds and also +which defines whether the match succeeds and also what values are produced.

    @@ -624,27 +701,35 @@

    lpeg.Carg (n)

    -

    lpeg.Cb (name)

    +

    lpeg.Cb (key)

    Creates a back capture. This pattern matches the empty string and produces the values produced by the most recent -group capture named name. +group capture named key +(where key can be any Lua value).

    Most recent means the last complete outermost -group capture with the given name. +group capture with the given key. A Complete capture means that the entire pattern -corresponding to the capture has matched. +corresponding to the capture has matched; +in other words, the back capture is not nested inside the group. An Outermost capture means that the capture is not inside -another complete capture. +another complete capture that does not contain the back capture itself.

    +

    +In the same way that LPeg does not specify when it evaluates captures, +it does not specify whether it reuses +values previously produced by the group +or re-evaluates them. +

    -

    lpeg.Cc ({value})

    +

    lpeg.Cc ([value, ...])

    Creates a constant capture. This pattern matches the empty string and @@ -654,62 +739,23 @@

    lpeg.Cc ({value})

    lpeg.Cf (patt, func)

    -Creates an fold capture. -If patt produces a list of captures -C1 C2 ... Cn, -this capture will produce the value -func(...func(func(C1, C2), C3)..., - Cn), -that is, it will fold -(or accumulate, or reduce) -the captures from patt using function func. -

    - -

    -This capture assumes that patt should produce -at least one capture with at least one value (of any type), -which becomes the initial value of an accumulator. -(If you need a specific initial value, -you may prefix a constant capture to patt.) -For each subsequent capture -LPeg calls func -with this accumulator as the first argument and all values produced -by the capture as extra arguments; -the value returned by this call -becomes the new value for the accumulator. -The final value of the accumulator becomes the captured value. -

    - -

    -As an example, -the following pattern matches a list of numbers separated -by commas and returns their addition: -

    -
    --- matches a numeral and captures its value
    -number = lpeg.R"09"^1 / tonumber
    -
    --- matches a list of numbers, captures their values
    -list = number * ("," * number)^0
    -
    --- auxiliary function to add two numbers
    -function add (acc, newvalue) return acc + newvalue end
    -
    --- folds the list of numbers adding them
    -sum = lpeg.Cf(list, add)
    -
    --- example of use
    -print(sum:match("10,30,43"))   --> 83
    -
    +Creates a fold capture. +This construction is deprecated; +use an accumulator pattern instead. +In general, a fold like +lpeg.Cf(p1 * p2^0, func) +can be translated to +(p1 * (p2 % func)^0). -

    lpeg.Cg (patt [, name])

    +

    lpeg.Cg (patt [, key])

    Creates a group capture. It groups all values returned by patt into a single capture. -The group may be anonymous (if no name is given) -or named with the given name. +The group may be anonymous (if no key is given) +or named with the given key +(which can be any non-nil Lua value).

    @@ -748,13 +794,13 @@

    lpeg.Cs (patt)

    lpeg.Ct (patt)

    Creates a table capture. -This capture creates a table and puts all values from all anonymous captures +This capture returns a table with all values from all anonymous captures made by patt inside this table in successive integer keys, starting at 1. Moreover, for each named capture group created by patt, the first value of the group is put into the table -with the group name as its key. +with the group key as its key. The captured value is only the table.

    @@ -770,9 +816,21 @@

    patt / string

    stands for the match of the n-th capture in patt. The sequence %0 stands for the whole match. The sequence %% stands for a single %. +

    + + +

    patt / number

    +

    +Creates a numbered capture. +For a non-zero number, +the captured value is the n-th value +captured by patt. +When number is zero, +there are no captured values. +

    -

    patt / table

    +

    patt / table

    Creates a query capture. It indexes the given table using as key the first value captured by @@ -798,17 +856,110 @@

    patt / function

    +

    patt % function

    +

    +Creates an accumulator capture. +This pattern behaves similarly to a +function capture, +with the following differences: +The last captured value before patt +is added as a first argument to the call; +the return of the function is adjusted to one single value; +that value replaces the last captured value. +Note that the capture itself produces no values; +it only changes the value of its previous capture. +

    + +

    +As an example, +let us consider the problem of adding a list of numbers. +

    +
    +-- matches a numeral and captures its numerical value
    +number = lpeg.R"09"^1 / tonumber
    +
    +-- auxiliary function to add two numbers
    +function add (acc, newvalue) return acc + newvalue end
    +
    +-- matches a list of numbers, adding their values
    +sum = number * ("," * number % add)^0
    +
    +-- example of use
    +print(sum:match("10,30,43"))   --> 83
    +
    +

    +First, the initial number captures a number; +that first capture will play the role of an accumulator. +Then, each time the sequence comma-number +matches inside the loop there is an accumulator capture: +It calls add with the current value of the +accumulator—which is the last captured value, created by the +first number— and the value of the new number, +and the result of the call (the sum of the two numbers) +replaces the value of the accumulator. +At the end of the match, +the accumulator with all sums is the final value. +

    + +

    +As another example, +consider the following code fragment: +

    +
    +local name = lpeg.C(lpeg.R("az")^1)
    +local p = name * (lpeg.P("^") % string.upper)^-1
    +print(p:match("count"))    --> count
    +print(p:match("count^"))   --> COUNT
    +
    +

    +In the match against "count", +as there is no "^", +the optional accumulator capture does not match; +so, the match results in its sole capture, a name. +In the match against "count^", +the accumulator capture matches, +so the function string.upper +is called with the previous captured value (created by name) +plus the string "^"; +the function ignores its second argument and returns the first argument +changed to upper case; +that value then becomes the first and only +capture value created by the match. +

    + +

    +Due to the nature of this capture, +you should avoid using it in places where it is not clear +what is the "previous" capture, +such as directly nested in a string capture +or a numbered capture. +(Note that these captures may not need to evaluate +all their subcaptures to compute their results.) +Moreover, due to implementation details, +you should not use this capture directly nested in a +substitution capture. +You should also avoid a direct nesting of this capture inside +a folding capture (deprecated), +as the folding will try to fold each individual accumulator capture. +A simple and effective way to avoid all these issues is +to enclose the whole accumulation composition +(including the capture that generates the initial value) +into an anonymous group capture. +

    + +

    lpeg.Cmt(patt, function)

    Creates a match-time capture. Unlike all other captures, -this one is evaluated immediately when a match occurs. +this one is evaluated immediately when a match occurs +(even if it is part of a larger pattern that fails later). It forces the immediate evaluation of all its nested captures and then calls function.

    -The function gets as arguments the entire subject, +The given function gets as arguments the entire subject, the current position (after the match of patt), plus any capture values produced by patt.

    @@ -821,6 +972,9 @@

    lpeg.Cmt(patt, function)

    and the returned number becomes the new current position. (Assuming a subject s and current position i, the returned number must be in the range [i, len(s) + 1].) +If the call returns true, +the match succeeds without consuming any input. +(So, to return true is equivalent to return i.) If the call returns false, nil, or no value, the match fails.

    @@ -835,9 +989,65 @@

    lpeg.Cmt(patt, function)

    Some Examples

    +

    Using a Pattern

    +

    +This example shows a very simple but complete program +that builds and uses a pattern: +

    +
    +local lpeg = require "lpeg"
    +
    +-- matches a word followed by end-of-string
    +p = lpeg.R"az"^1 * -1
    +
    +print(p:match("hello"))        --> 6
    +print(lpeg.match(p, "hello"))  --> 6
    +print(p:match("1 hello"))      --> nil
    +
    +

    +The pattern is simply a sequence of one or more lower-case letters +followed by the end of string (-1). +The program calls match both as a method +and as a function. +In both sucessful cases, +the match returns +the index of the first character after the match, +which is the string length plus one. +

    + + +

    Name-value lists

    +

    +This example parses a list of name-value pairs and returns a table +with those pairs: +

    +
    +lpeg.locale(lpeg)   -- adds locale entries into 'lpeg' table
    +
    +local space = lpeg.space^0
    +local name = lpeg.C(lpeg.alpha^1) * space
    +local sep = lpeg.S(",;") * space
    +local pair = name * "=" * space * name * sep^-1
    +local list = lpeg.Ct("") * (pair % rawset)^0
    +t = list:match("a=b, c = hi; next = pi")
    +        --> { a = "b", c = "hi", next = "pi" }
    +
    +

    +Each pair has the format name = name followed by +an optional separator (a comma or a semicolon). +The list pattern then folds these captures. +It starts with an empty table, +created by a table capture matching an empty string; +then for each a pair of names it applies rawset +over the accumulator (the table) and the capture values (the pair of names). +rawset returns the table itself, +so the accumulator is always the table. +

    +

    Splitting a string

    -The following code splits a string using a given pattern +The following code builds a pattern that +splits a string using a given pattern sep as a separator:

    @@ -852,7 +1062,8 @@ 

    Splitting a string

    First the function ensures that sep is a proper pattern. The pattern elem is a repetition of zero of more arbitrary characters as long as there is not a match against -the separator. It also captures its result. +the separator. +It also captures its match. The pattern p matches a list of elements separated by sep.

    @@ -861,8 +1072,8 @@

    Splitting a string

    If the split results in too many values, it may overflow the maximum number of values that can be returned by a Lua function. -In this case, -we should collect these values in a table: +To avoid this problem, +we can collect these values in a table:

     function split (s, sep)
    @@ -897,7 +1108,7 @@ 

    Searching for a pattern

    This grammar has a straight reading: -it matches p or skips one character and tries again. +its sole rule matches p or skips one character and tries again.

    @@ -906,25 +1117,27 @@

    Searching for a pattern

    we can add position captures to the pattern:

    -local I = lpeg.Cp()
    +local Cp = lpeg.Cp()
     function anywhere (p)
    -  return lpeg.P{ I * p * I + 1 * lpeg.V(1) }
    +  return lpeg.P{ Cp * p * Cp + 1 * lpeg.V(1) }
     end
    +
    +print(anywhere("world"):match("hello world!"))   --> 7   12
     

    Another option for the search is like this:

    -local I = lpeg.Cp()
    +local Cp = lpeg.Cp()
     function anywhere (p)
    -  return (1 - lpeg.P(p))^0 * I * p * I
    +  return (1 - lpeg.P(p))^0 * Cp * p * Cp
     end
     

    Again the pattern has a straight reading: it skips as many characters as possible while not matching p, -and then matches p (plus appropriate captures). +and then matches p plus appropriate captures.

    @@ -982,36 +1195,6 @@

    Global substitution

    -

    Name-value lists

    -

    -This example parses a list of name-value pairs and returns a table -with those pairs: -

    -
    -lpeg.locale(lpeg)
    -
    -local space = lpeg.space^0
    -local name = lpeg.C(lpeg.alpha^1) * space
    -local sep = lpeg.S(",;") * space
    -local pair = lpeg.Cg(name * "=" * space * name) * sep^-1
    -local list = lpeg.Cf(lpeg.Ct("") * pair^0, rawset)
    -t = list:match("a=b, c = hi; next = pi")  --> { a = "b", c = "hi", next = "pi" }
    -
    -

    -Each pair has the format name = name followed by -an optional separator (a comma or a semicolon). -The pair pattern encloses the pair in a group pattern, -so that the names become the values of a single capture. -The list pattern then folds these captures. -It starts with an empty table, -created by a table capture matching an empty string; -then for each capture (a pair of names) it applies rawset -over the accumulator (the table) and the capture values (the pair of names). -rawset returns the table itself, -so the accumulator is always the table. -

    - -

    Comma-Separated Values (CSV)

    This example breaks a string into comma-separated values, @@ -1037,89 +1220,15 @@

    Comma-Separated Values (CSV)

    ending with a newline or the string end (-1).

    - -

    UTF-8 and Latin 1

    -It is not difficult to use LPeg to convert a string from -UTF-8 encoding to Latin 1 (ISO 8859-1): +As it is, +the previous pattern returns each field as a separated result. +If we add a table capture in the definition of record, +the pattern will return instead a single table +containing all fields:

    - -
    --- convert a two-byte UTF-8 sequence to a Latin 1 character
    -local function f2 (s)
    -  local c1, c2 = string.byte(s, 1, 2)
    -  return string.char(c1 * 64 + c2 - 12416)
    -end
    -
    -local utf8 = lpeg.R("\0\127")
    -           + lpeg.R("\194\195") * lpeg.R("\128\191") / f2
    -
    -local decode_pattern = lpeg.Cs(utf8^0) * -1
    -
    -

    -In this code, -the definition of UTF-8 is already restricted to the -Latin 1 range (from 0 to 255). -Any encoding outside this range (as well as any invalid encoding) -will not match that pattern. -

    - -

    -As the definition of decode_pattern demands that -the pattern matches the whole input (because of the -1 at its end), -any invalid string will simply fail to match, -without any useful information about the problem. -We can improve this situation redefining decode_pattern -as follows: -

    -
    -local function er (_, i) error("invalid encoding at position " .. i) end
    -
    -local decode_pattern = lpeg.Cs(utf8^0) * (-1 + lpeg.P(er))
    -
    -

    -Now, if the pattern utf8^0 stops -before the end of the string, -an appropriate error function is called. -

    - - -

    UTF-8 and Unicode

    -

    -We can extend the previous patterns to handle all Unicode code points. -Of course, -we cannot translate them to Latin 1 or any other one-byte encoding. -Instead, our translation results in a array with the code points -represented as numbers. -The full code is here: -

    -
    --- decode a two-byte UTF-8 sequence
    -local function f2 (s)
    -  local c1, c2 = string.byte(s, 1, 2)
    -  return c1 * 64 + c2 - 12416
    -end
    -
    --- decode a three-byte UTF-8 sequence
    -local function f3 (s)
    -  local c1, c2, c3 = string.byte(s, 1, 3)
    -  return (c1 * 64 + c2) * 64 + c3 - 925824
    -end
    -
    --- decode a four-byte UTF-8 sequence
    -local function f4 (s)
    -  local c1, c2, c3, c4 = string.byte(s, 1, 4)
    -  return ((c1 * 64 + c2) * 64 + c3) * 64 + c4 - 63447168
    -end
    -
    -local cont = lpeg.R("\128\191")   -- continuation byte
    -
    -local utf8 = lpeg.R("\0\127") / string.byte
    -           + lpeg.R("\194\223") * cont / f2
    -           + lpeg.R("\224\239") * cont * cont / f3
    -           + lpeg.R("\240\244") * cont * cont * cont / f4
    -
    -local decode_pattern = lpeg.Ct(utf8^0) * -1
    +
    +local record = lpeg.Ct(field * (',' * field)^0) * (lpeg.P'\n' + -1)
     
    @@ -1129,7 +1238,7 @@

    Lua's long strings

    and ends at the first occurrence of ]=*] with exactly the same number of equal signs. If the opening brackets are followed by a newline, -this newline is discharged +this newline is discarded (that is, it is not part of the string).

    @@ -1141,18 +1250,19 @@

    Lua's long strings

    -open = "[" * lpeg.Cg(lpeg.P"="^0, "init") * "[" * lpeg.P"\n"^-1
    -close = "]" * lpeg.C(lpeg.P"="^0) * "]"
    +equals = lpeg.P"="^0
    +open = "[" * lpeg.Cg(equals, "init") * "[" * lpeg.P"\n"^-1
    +close = "]" * lpeg.C(equals) * "]"
     closeeq = lpeg.Cmt(close * lpeg.Cb("init"), function (s, i, a, b) return a == b end)
    -string = open * m.C((lpeg.P(1) - closeeq)^0) * close /
    -  function (o, s) return s end
    +string = open * lpeg.C((lpeg.P(1) - closeeq)^0) * close / 1
     

    The open pattern matches [=*[, capturing the repetitions of equal signs in a group named init; it also discharges an optional newline, if present. -The close pattern matches ]=*]. +The close pattern matches ]=*], +also capturing the repetitions of equal signs. The closeeq pattern first matches close; then it uses a back capture to recover the capture made by the previous open, @@ -1162,10 +1272,8 @@

    Lua's long strings

    The string pattern starts with an open, then it goes as far as possible until matching closeeq, and then matches the final close. -The final function capture simply consumes -the captures made by open and close -and returns only the middle capture, -which is the string contents. +The final numbered capture simply discards +the capture made by close.

    @@ -1181,17 +1289,17 @@

    Arithmetic expressions

    -- Lexical Elements local Space = lpeg.S(" \n\t")^0 local Number = lpeg.C(lpeg.P"-"^-1 * lpeg.R("09")^1) * Space -local FactorOp = lpeg.C(lpeg.S("+-")) * Space -local TermOp = lpeg.C(lpeg.S("*/")) * Space +local TermOp = lpeg.C(lpeg.S("+-")) * Space +local FactorOp = lpeg.C(lpeg.S("*/")) * Space local Open = "(" * Space local Close = ")" * Space -- Grammar local Exp, Term, Factor = lpeg.V"Exp", lpeg.V"Term", lpeg.V"Factor" G = lpeg.P{ Exp, - Exp = lpeg.Ct(Factor * (FactorOp * Factor)^0); - Factor = lpeg.Ct(Term * (TermOp * Term)^0); - Term = Number + Open * Exp * Close; + Exp = lpeg.Ct(Term * (TermOp * Term)^0); + Term = lpeg.Ct(Factor * (FactorOp * Factor)^0); + Factor = Number + Open * Exp * Close; } G = Space * G * -1 @@ -1223,7 +1331,7 @@

    Arithmetic expressions

    end -- small example -print(evalExp"3 + 5*9 / (1+1) - 12") --> 13.5 +print(evalExp"3 + 5*9 / (1+1) - 12") --> 13.5

    @@ -1245,21 +1353,21 @@

    Arithmetic expressions

    -- Grammar local V = lpeg.V G = lpeg.P{ "Exp", - Exp = lpeg.Cf(V"Factor" * lpeg.Cg(FactorOp * V"Factor")^0, eval); - Factor = lpeg.Cf(V"Term" * lpeg.Cg(TermOp * V"Term")^0, eval); - Term = Number / tonumber + Open * V"Exp" * Close; + Exp = V"Term" * (TermOp * V"Term" % eval)^0; + Term = V"Factor" * (FactorOp * V"Factor" % eval)^0; + Factor = Number / tonumber + Open * V"Exp" * Close; } -- small example -print(lpeg.match(G, "3 + 5*9 / (1+1) - 12")) --> 13.5 +print(lpeg.match(G, "3 + 5*9 / (1+1) - 12")) --> 13.5

    -Note the use of the fold (accumulator) capture. +Note the use of the accumulator capture. To compute the value of an expression, -the accumulator starts with the value of the first factor, +the accumulator starts with the value of the first term, and then applies eval over the accumulator, the operator, -and the new factor for each repetition. +and the new term for each repetition.

    @@ -1267,13 +1375,20 @@

    Arithmetic expressions

    Download

    LPeg -source code.

    +source code.

    + +

    +Probably, the easiest way to install LPeg is with +LuaRocks. +If you have LuaRocks installed, +the following command is all you need to install LPeg: +

    $ luarocks install lpeg

    License

    -Copyright © 2008 Lua.org, PUC-Rio. +Copyright © 2007-2023 Lua.org, PUC-Rio.

    Permission is hereby granted, free of charge, @@ -1309,12 +1424,6 @@

    License

  • -
    -

    -$Id: lpeg.html,v 1.54 2008/10/10 19:07:32 roberto Exp $ -

    -
    - diff --git a/files/docs/lpeg/re.html b/files/docs/lpeg/re.html index 9b55da7..c8d1bc8 100755 --- a/files/docs/lpeg/re.html +++ b/files/docs/lpeg/re.html @@ -1,22 +1,21 @@ + "//www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> LPeg.re - Regex syntax for LPEG -
    @@ -46,7 +45,7 @@

    re

    The re Module

    -The re Module +The re module (provided by file re.lua in the distribution) supports a somewhat conventional regex syntax for pattern usage within LPeg. @@ -57,49 +56,57 @@

    The re Module

    A p represents an arbitrary pattern; num represents a number ([0-9]+); name represents an identifier -([a-zA-Z][a-zA-Z0-9]*). +([a-zA-Z][a-zA-Z0-9_]*). Constructions are listed in order of decreasing precedence. + + + + + + + + + + + + + + + + - - - - - - - - - + + + - + - - - - - + +
    SyntaxDescription
    ( p ) grouping
    & p and predicate
    ! p not predicate
    p1 p2 concatenation
    p1 / p2 ordered choice
    p ? optional match
    p * zero or more repetitions
    p + one or more repetitions
    p^numexactly num repetitions
    p^+numat least num repetitions
    p^-numat most num repetitions
    (name <- p)+ grammar
    'string' literal string
    "string" literal string
    [class] character class
    . any character
    %name pattern defs[name] or a pre-defined pattern
    namenon terminal
    <name>non terminal
    {} position capture
    { p } simple capture
    {: p :} anonymous group capture
    {:name: p :} named group capture
    {~ p ~} substitution capture
    =name back reference -
    p ? optional match
    p * zero or more repetitions
    p + one or more repetitions
    p^num exactly n repetitions
    p^+numat least n repetitions
    p^-numat most n repetitions
    {| p |} table capture
    =name back reference
    p -> 'string' string capture
    p -> "string" string capture
    p -> {} table capture
    p -> num numbered capture
    p -> name function/query/string capture equivalent to p / defs[name]
    p => name match-time capture equivalent to lpeg.Cmt(p, defs[name])
    & p and predicate
    ! p not predicate
    p1 p2 concatenation
    p1 / p2 ordered choice
    (name <- p)+ grammar
    p ~> name fold capture +(deprecated)
    p >> name accumulator capture +equivalent to (p % defs[name])

    Any space appearing in a syntax description can be -replaced by zero or more space characters and Lua-style comments +replaced by zero or more space characters and Lua-style short comments (-- until end of line).

    @@ -148,14 +155,28 @@

    re.find (subject, pattern [, init])

    Searches the given pattern in the given subject. If it finds a match, -returns the index where this occurrence starts, -plus the captures made by the pattern (if any). +returns the index where this occurrence starts and +the index where it ends. Otherwise, returns nil.

    +

    +An optional numeric argument init makes the search +starts at that position in the subject string. +As usual in Lua libraries, +a negative value counts from the end. +

    + +

    re.gsub (subject, pattern, replacement)

    +

    +Does a global substitution, +replacing all occurrences of pattern +in the given subject by replacement. +

    re.match (subject, pattern)

    -Matches the given pattern against the given subject. +Matches the given pattern against the given subject, +returning all captures.

    re.updatelocale ()

    @@ -166,15 +187,39 @@

    re.updatelocale ()

    Some Examples

    +

    A complete simple program

    +

    +The next code shows a simple complete Lua program using +the re module: +

    +
    +local re = require"re"
    +
    +-- find the position of the first numeral in a string
    +print(re.find("the number 423 is odd", "[0-9]+"))  --> 12    14
    +
    +-- returns all words in a string
    +print(re.match("the number 423 is odd", "({%a+} / .)*"))
    +--> the    number    is    odd
    +
    +-- returns the first numeral in a string
    +print(re.match("the number 423 is odd", "s <- {%d+} / . s"))
    +--> 423
    +
    +-- substitutes a dot for each vowel in a string
    +print(re.gsub("hello World", "[aeiou]", "."))
    +--> h.ll. W.rld
    +
    + +

    Balanced parentheses

    -As a simple example, -the following call will produce the same pattern produced by the +The following call will produce the same pattern produced by the Lua expression in the balanced parentheses example:

    -b = re.compile[[  balanced <- "(" ([^()] / <balanced>)* ")"  ]]
    +b = re.compile[[  balanced <- "(" ([^()] / balanced)* ")"  ]]
     

    String reversal

    @@ -182,7 +227,7 @@

    String reversal

    The next example reverses a string:

    -rev = re.compile[[ R <- (!.) -> '' / ({.} <R>) -> '%2%1']]
    +rev = re.compile[[ R <- (!.) -> '' / ({.} R) -> '%2%1']]
     print(rev:match"0123456789")   --> 9876543210
     
    @@ -192,8 +237,8 @@

    CSV decoder

     record = re.compile[[
    -  record <- ( <field> (',' <field>)* ) -> {} (%nl / !.)
    -  field <- <escaped> / <nonescaped>
    +  record <- {| field (',' field)* |} (%nl / !.)
    +  field <- escaped / nonescaped
       nonescaped <- { [^,"%nl]* }
       escaped <- '"' {~ ([^"] / '""' -> '"')* ~} '"'
     ]]
    @@ -201,17 +246,85 @@ 

    CSV decoder

    Lua's long strings

    -The next example mathes Lua long strings: +The next example matches Lua long strings:

     c = re.compile([[
    -  longstring <- ('[' {:eq: '='* :} '[' <close>) => void
    -  close <- ']' =eq ']' / . <close>
    -]], {void = function () return true end})
    +  longstring <- ('[' {:eq: '='* :} '[' close)
    +  close <- ']' =eq ']' / . close
    +]])
     
     print(c:match'[==[]]===]]]]==]===[]')   --> 17
     
    +

    Abstract Syntax Trees

    +

    +This example shows a simple way to build an +abstract syntax tree (AST) for a given grammar. +To keep our example simple, +let us consider the following grammar +for lists of names: +

    +
    +p = re.compile[[
    +      listname <- (name s)*
    +      name <- [a-z][a-z]*
    +      s <- %s*
    +]]
    +
    +

    +Now, we will add captures to build a corresponding AST. +As a first step, the pattern will build a table to +represent each non terminal; +terminals will be represented by their corresponding strings: +

    +
    +c = re.compile[[
    +      listname <- {| (name s)* |}
    +      name <- {| {[a-z][a-z]*} |}
    +      s <- %s*
    +]]
    +
    +

    +Now, a match against "hi hello bye" +results in the table +{{"hi"}, {"hello"}, {"bye"}}. +

    +

    +For such a simple grammar, +this AST is more than enough; +actually, the tables around each single name +are already overkilling. +More complex grammars, +however, may need some more structure. +Specifically, +it would be useful if each table had +a tag field telling what non terminal +that table represents. +We can add such a tag using +named group captures: +

    +
    +x = re.compile[[
    +      listname <- {| {:tag: '' -> 'list':} (name s)* |}
    +      name <- {| {:tag: '' -> 'id':} {[a-z][a-z]*} |}
    +      s <- ' '*
    +]]
    +
    +

    +With these group captures, +a match against "hi hello bye" +results in the following table: +

    +
    +{tag="list",
    +  {tag="id", "hi"},
    +  {tag="id", "hello"},
    +  {tag="id", "bye"}
    +}
    +
    + +

    Indented blocks

    This example breaks indented blocks into tables, @@ -219,8 +332,8 @@

    Indented blocks

     p = re.compile[[
    -  block <- ({:ident:' '*:} <line>
    -           ((=ident !' ' <line>) / &(=ident ' ') <block>)*) -> {}
    +  block <- {| {:ident:' '*:} line
    +           ((=ident !' ' line) / &(=ident ' ') block)* |}
       line <- {[^%nl]*} %nl
     ]]
     
    @@ -259,14 +372,14 @@

    Macro expander

     p = re.compile[[
    -      text <- {~ <item>* ~}
    -      item <- <macro> / [^()] / '(' <item>* ')'
    -      arg <- ' '* {~ (!',' <item>)* ~}
    -      args <- '(' <arg> (',' <arg>)* ')'
    +      text <- {~ item* ~}
    +      item <- macro / [^()] / '(' item* ')'
    +      arg <- ' '* {~ (!',' item)* ~}
    +      args <- '(' arg (',' arg)* ')'
           -- now we define some macros
    -      macro <- ('apply' <args>) -> '%1(%2)'
    -             / ('add' <args>) -> '%1 + %2'
    -             / ('mul' <args>) -> '%1 * %2'
    +      macro <- ('apply' args) -> '%1(%2)'
    +             / ('add' args) -> '%1 + %2'
    +             / ('mul' args) -> '%1 * %2'
     ]]
     
     print(p:match"add(mul(a,b), apply(f,x))")   --> a * b + f(x)
    @@ -290,54 +403,69 @@ 

    Macro expander

    with each %n replaced by the n-th argument.

    +

    Patterns

    +

    +This example shows the complete syntax +of patterns accepted by re. +

    +
    +p = [=[
    +
    +pattern         <- exp !.
    +exp             <- S (grammar / alternative)
    +
    +alternative     <- seq ('/' S seq)*
    +seq             <- prefix*
    +prefix          <- '&' S prefix / '!' S prefix / suffix
    +suffix          <- primary S (([+*?]
    +                            / '^' [+-]? num
    +                            / '->' S (string / '{}' / name)
    +                            / '>>' S name
    +                            / '=>' S name) S)*
    +
    +primary         <- '(' exp ')' / string / class / defined
    +                 / '{:' (name ':')? exp ':}'
    +                 / '=' name
    +                 / '{}'
    +                 / '{~' exp '~}'
    +                 / '{|' exp '|}'
    +                 / '{' exp '}'
    +                 / '.'
    +                 / name S !arrow
    +                 / '<' name '>'          -- old-style non terminals
    +
    +grammar         <- definition+
    +definition      <- name S arrow exp
    +
    +class           <- '[' '^'? item (!']' item)* ']'
    +item            <- defined / range / .
    +range           <- . '-' [^]]
    +
    +S               <- (%s / '--' [^%nl]*)*   -- spaces and comments
    +name            <- [A-Za-z_][A-Za-z0-9_]*
    +arrow           <- '<-'
    +num             <- [0-9]+
    +string          <- '"' [^"]* '"' / "'" [^']* "'"
    +defined         <- '%' name
    +
    +]=]
    +
    +print(re.match(p, p))   -- a self description must match itself
    +

    License

    -Copyright © 2008 Lua.org, PUC-Rio. -

    -

    -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), -to deal in the Software without restriction, -including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, -and to permit persons to whom the Software is -furnished to do so, -subject to the following conditions: -

    +This module is part of the LPeg package and shares +its license. -

    -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. -

    -
    -

    -$Id: re.html,v 1.11 2008/10/10 18:14:06 roberto Exp $ -

    -
    - diff --git a/files/docs/luajson/LuaJSON.txt b/files/docs/luajson/LuaJSON.txt index c18e2e4..242d37c 100644 --- a/files/docs/luajson/LuaJSON.txt +++ b/files/docs/luajson/LuaJSON.txt @@ -10,7 +10,7 @@ luajson - JSON encoder/decoder for Lua SYNOPSIS -------- -require("json") +local json = require("json") json.decode("json-string" [, parameters]) @@ -112,7 +112,14 @@ number.inf = false unicodeWhitespace : boolean:: Specifies if unicode whitespace characters are counted -array.trailingComma / object.trailingComma : boolean:: +array.allowEmptyElement / calls.allowEmptyElement / object.allowEmptyElement : boolean:: + Specifies if missing values are allowed, replacing them with the value of 'undefined' or 'null' if undefined not allowed. + Example cases: + [1,, 3] ==> { 1, json.util.undefined, 3 } + { x: } ==> { x = json.util.undefined } + call(1,, 3) ==> call(1, json.util.undefined, 3) + +array.trailingComma / calls.trailingComma / object.trailingComma : boolean:: Specifies if extraneous trailing commas are ignored in declaration calls.defs : map:: diff --git a/files/examples/iup/getparam.wlua b/files/examples/iup/getparam.wlua index 387c846..415cd53 100644 --- a/files/examples/iup/getparam.wlua +++ b/files/examples/iup/getparam.wlua @@ -5,12 +5,14 @@ require( "iuplua" ) require( "iupluacontrols" ) function param_action(dialog, param_index) - if (param_index == -1) then + if (param_index == iup.GETPARAM_OK) then print("OK") - elseif (param_index == -2) then + elseif (param_index == iup.GETPARAM_INIT) then print("Map") - elseif (param_index == -3) then + elseif (param_index == iup.GETPARAM_CANCEL) then print("Cancel") + elseif (param_index == iup.GETPARAM_HELP) then + print("Help") else local param = iup.GetParamParam(dialog, param_index) print("PARAM"..param_index.." = "..param.value) diff --git a/files/examples/iup/globalcb.wlua b/files/examples/iup/globalcb.wlua new file mode 100644 index 0000000..7bf53d5 --- /dev/null +++ b/files/examples/iup/globalcb.wlua @@ -0,0 +1,13 @@ + +function gbutton_cb(but, press, x, y, status) + print(but, press, x, y, status) +end + +function gkeypress_cb(code, pressed) + print(code, pressed) +end + +iup.SetGlobal("INPUTCALLBACKS", "Yes") +iup.SetGlobalCallback("GLOBALBUTTON_CB", gbutton_cb) +iup.SetGlobalCallback("GLOBALKEYPRESS_CB", gkeypress_cb) + diff --git a/files/examples/iup/html_clipboard.lua b/files/examples/iup/html_clipboard.lua new file mode 100644 index 0000000..fbe6763 --- /dev/null +++ b/files/examples/iup/html_clipboard.lua @@ -0,0 +1,37 @@ +-- StartHTML = byte count to just before in this case, could be a more complex header +-- EndHTML = byte count to the last byte, not counting the extra NUL +-- StartFragment = byte count just after the +-- EndFragment = byte count just before the + +-- Notice that byte count must include the line feed, +-- if DOS format is used must count LF by 2 +-- Notice thatcharacter encoding is UTF-8, +-- so if string has extended characters must converto to UTF-8 before copying + +-- Tested on Windows + +html = [[Version:0.9 +StartHTML:000084 +EndHTML:000242 +StartFragment:000163 +EndFragment:000207 + + +HTML clipboard + + +a copy to the clip + + +]] .. "\0" -- Extra NUL for UTF-8 + +clipboard = iup:clipboard() + +clipboard.text = nil -- clear contents + +clipboard.text = "a copy to the clip" -- copy also as text + +clipboard.addformat = 'HTML Format' -- copy as HTML +clipboard.format = 'HTML Format' +clipboard.formatdatasize = html:len()+1 +clipboard.formatdata = html diff --git a/files/examples/iup/image.wlua b/files/examples/iup/image.wlua index eb6ba45..16b954f 100644 --- a/files/examples/iup/image.wlua +++ b/files/examples/iup/image.wlua @@ -60,25 +60,25 @@ img_cursor = iup.image{ } -- Creates a button and associates image img_x to it -btn = iup.button{ image = img_x } +btn = iup.button{ image = img_x, title = "btn" } -- Creates a label and associates image img_x to it -lbl = iup.label{ image = img_x } +lbl = iup.label{ image = img_x, title = "lbl" } -- Creates toggle and associates image img_x to it -tgl = iup.toggle{ image = img_x } +tgl = iup.toggle{ image = img_x, title = "tgl" } -- Creates two toggles and associates image img_x to them -tgl_radio_1 = iup.toggle{ image = img_x } -tgl_radio_2 = iup.toggle{ image = img_x } +tgl_radio_1 = iup.toggle{ image = img_x, title = "tgl_radio_1" } +tgl_radio_2 = iup.toggle{ image = img_x, title = "tgl_radio_2" } -- Creates label showing image size lbl_size = iup.label{ title = '"X" image width = '..img_x.width..'; "X" image height = '..img_x.height } -- Creates frames around the elements -frm_btn = iup.frame{btn; title="button", size="EIGHTHxEIGHTH"} -frm_lbl = iup.frame{lbl; title="label" , size="EIGHTHxEIGHTH"} -frm_tgl = iup.frame{tgl; title="toggle", size="EIGHTHxEIGHTH"} +frm_btn = iup.frame{btn; title="button"} +frm_lbl = iup.frame{lbl; title="label" } +frm_tgl = iup.frame{tgl; title="toggle"} frm_tgl_radio = iup.frame{ iup.radio{ diff --git a/files/examples/iup/list.wlua b/files/examples/iup/list.wlua index 5adeadc..ff7216b 100644 --- a/files/examples/iup/list.wlua +++ b/files/examples/iup/list.wlua @@ -6,6 +6,8 @@ require( "iuplua" ) -- Creates a list and sets items, initial item and size list = iup.list {"Gold", "Silver", "Bronze", "None" ; value = 4, size = "EIGHTHxEIGHTH"} + +list[5] = "test" -- Creates frame with simple list and sets its title frm_medal = iup.frame {list ; title = "Best medal"} diff --git a/files/examples/iup/matrix_fittosize.lua b/files/examples/iup/matrix_fittosize.lua new file mode 100644 index 0000000..e44d6f9 --- /dev/null +++ b/files/examples/iup/matrix_fittosize.lua @@ -0,0 +1,42 @@ +--require( "iuplua" ) +--require( "iupluacontrols" ) + +mat = iup.matrix { + numcol=5, + numlin=3, + numcol_visible=5, + numlin_visible=3, + expand = "HORIZONTAL", + resizematrix = "YES"} + +mat:setcell(0,0,"Inflation") +mat:setcell(1,0,"Medicine") +mat:setcell(2,0,"Food") +mat:setcell(3,0,"Energy") +mat:setcell(0,1,"January 2000") +mat:setcell(0,2,"February 2000") +mat:setcell(1,1,"5.6") +mat:setcell(2,1,"2.2") +mat:setcell(3,1,"7.2") +mat:setcell(1,2,"4.6") +mat:setcell(2,2,"1.3") +mat:setcell(3,2,"1.4") + +dlg = iup.dialog{iup.vbox{mat; margin="10x10"}; shrink="yes"} + +function dlg:resize_cb(w, h) + iup.Refresh(dlg) + mat.rasterwidth1 = nil + mat.rasterwidth2 = nil + mat.rasterwidth3 = nil + mat.rasterwidth4 = nil + mat.rasterwidth5 = nil + mat.fittosize = "columns" + return iup.IGNORE +end + +dlg:showxy(iup.CENTER, iup.CENTER) + +if (iup.MainLoopLevel()==0) then + iup.MainLoop() +end diff --git a/files/examples/iup/normalizer1.wlua b/files/examples/iup/normalizer1.wlua new file mode 100644 index 0000000..b6a2156 --- /dev/null +++ b/files/examples/iup/normalizer1.wlua @@ -0,0 +1,34 @@ +require'iuplua' + +local label1 = iup.label{title = 'param 1'} +local label2 = iup.label{title = 'param 2 with long name'} + +local norm = iup.normalizer{label1, label2} +norm.normalize = "HORIZONTAL" +norm:destroy() + +local dialog = iup.dialog +{ + title="a dialog", size="QUARTERxQUARTER", + iup.vbox + { + cgap = "5x5", + cmargin = "5x5", + iup.hbox + { + label1, + iup.text{}, + }, + iup.hbox + { + label2, + iup.text{}, + } + } +} + +dialog:show() +iup.MainLoop() + +dialog:destroy() +iup.Close() diff --git a/files/examples/iup/normalizer2.wlua b/files/examples/iup/normalizer2.wlua new file mode 100644 index 0000000..dd6f2a8 --- /dev/null +++ b/files/examples/iup/normalizer2.wlua @@ -0,0 +1,28 @@ +require'iuplua' + +local norm = iup.normalizer{} +local dialog = iup.dialog +{ + title="a dialog", size="QUARTERxQUARTER", + iup.vbox + { + CGAP = "5x5", CMARGIN = "5x5", + iup.hbox + { + iup.label{title = 'param 1', NORMALIZERGROUP = norm}; + iup.text{}; + }; + iup.hbox + { + iup.label{title = 'param 2 with long name', NORMALIZERGROUP = norm}; + iup.text{}; + }; + }; +} +norm.normalize = "HORIZONTAL" +iup.Destroy(norm) + +dialog:show() +iup.MainLoop() + +dialog:destroy() diff --git a/files/examples/iup/pplot.wlua b/files/examples/iup/pplot.wlua index 59db4b5..656a2ed 100644 --- a/files/examples/iup/pplot.wlua +++ b/files/examples/iup/pplot.wlua @@ -1,6 +1,5 @@ require( "iuplua" ) -require( "iupluacontrols" ) -require( "iuplua_pplot" ) +require( "iuplua_pplot" ) plot = iup.pplot{ TITLE = "Simple Line", diff --git a/files/examples/iup/pplot_bar.wlua b/files/examples/iup/pplot_bar.wlua new file mode 100644 index 0000000..4a210e5 --- /dev/null +++ b/files/examples/iup/pplot_bar.wlua @@ -0,0 +1,44 @@ +require("iuplua") +require("iuplua_pplot") + +tblSurnames = {Blogs=4, +Smith=5, +Jones=12, +Weaver=3, +Whitesmith=4 +} + +plot = iup.pplot{ + title = "Surname Distribution", + marginbottom="65", + marginleft="65", + axs_xlabel=" ", + axs_ylabel="Count", + legendshow="YES", + legendpos="TOPLEFT", + axs_xmin = "0", + axs_ymin = "0", + axs_yautomin = "NO", + axs_xautomin = "NO", + axs_xautotick = "no", + ds_showvalues = "YES" +} + +iup.PPlotBegin(plot, 1) +i = 0 +for strSurname, iQty in pairs(tblSurnames) do + iup.PPlotAddStr(plot, strSurname, iQty) + i = i + 1 +end +iup.PPlotEnd(plot) + +plot.ds_mode = "BAR" +plot.ds_legend = "Surnames" + +d = iup.dialog{plot, size="800x200", title="PPlot"} + +d:show() + +if (iup.MainLoopLevel()==0) then + iup.MainLoop() +end diff --git a/files/examples/iup/text_highlight.wlua b/files/examples/iup/text_highlight.wlua new file mode 100644 index 0000000..9d3d524 --- /dev/null +++ b/files/examples/iup/text_highlight.wlua @@ -0,0 +1,14 @@ +require("iuplua") + +local text1= iup.text{value="Apenas PARTE DESTE TEXTO deve ficar em highlight", size="250", + readonly="YES", FORMATTING = "YES"} +local text2= iup.text{value="Nada deste texto deve ficar em highlight", size="250"} +local vbox= iup.vbox{text1,text2} + +local tags = iup.user { selectionpos = "7:24", bgcolor = "255 128 64" } +text1.addformattag = tags + +local dlg=iup.dialog{vbox} +dlg:show() + +iup.MainLoop() diff --git a/files/examples/lpeg/test.lua b/files/examples/lpeg/test.lua index ac85f31..d0b82da 100755 --- a/files/examples/lpeg/test.lua +++ b/files/examples/lpeg/test.lua @@ -1,11 +1,21 @@ -#!/usr/bin/env lua5.1 +#!/usr/bin/env lua --- $Id: test.lua,v 1.70 2008/10/09 20:16:45 roberto Exp $ +-- require"strict" -- just to be pedantic local m = require"lpeg" -any = m.P(1) -space = m.S" \t\n"^0 + +-- for general use +local a, b, c, d, e, f, g, p, t + + +-- compatibility with Lua 5.2 +local unpack = rawget(table, "unpack") or unpack +local loadstring = rawget(_G, "loadstring") or load + + +local any = m.P(1) +local space = m.S" \t\n"^0 local function checkeq (x, y, p) if p then print(x,y) end @@ -17,7 +27,7 @@ if p then print(x,y) end end -mt = getmetatable(m.P(1)) +local mt = getmetatable(m.P(1)) local allchar = {} @@ -36,8 +46,8 @@ end print"General tests for LPeg library" -assert(type(m.version()) == "string") -print("version " .. m.version()) +assert(type(m.version) == "string") +print(m.version) assert(m.type("alo") ~= "pattern") assert(m.type(io.input) ~= "pattern") assert(m.type(m.P"alo") == "pattern") @@ -58,10 +68,14 @@ assert(m.match(#m.P(true) * "a", "a") == 2) assert(m.match("a" * #m.P(false), "a") == nil) assert(m.match("a" * #m.P(true), "a") == 2) +assert(m.match(m.P(1)^0, "abcd") == 5) +assert(m.match(m.S("")^0, "abcd") == 1) -- tests for locale do assert(m.locale(m) == m) + local t = {} + assert(m.locale(t, m) == t) local x = m.locale() for n,v in pairs(x) do assert(type(n) == "string") @@ -70,7 +84,6 @@ do end - assert(m.match(3, "aaaa")) assert(m.match(4, "aaaa")) assert(not m.match(5, "aaaa")) @@ -85,11 +98,11 @@ assert(m.match("al", "alo") == 3) assert(not m.match("alu", "alo")) assert(m.match(true, "") == 1) -digit = m.S"0123456789" -upper = m.S"ABCDEFGHIJKLMNOPQRSTUVWXYZ" -lower = m.S"abcdefghijklmnopqrstuvwxyz" -letter = m.S"" + upper + lower -alpha = letter + digit + m.R() +local digit = m.S"0123456789" +local upper = m.S"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +local lower = m.S"abcdefghijklmnopqrstuvwxyz" +local letter = m.S"" + upper + lower +local alpha = letter + digit + m.R() eqcharset(m.S"", m.P(false)) eqcharset(upper, m.R("AZ")) @@ -106,7 +119,9 @@ eqcharset(m.S"\1\0\2", m.R"\0\2") eqcharset(m.S"\1\0\2", m.R"\1\2" + "\0") eqcharset(m.S"\1\0\2" - "\0", m.R"\1\2") -word = alpha^1 * (1 - alpha)^0 +eqcharset(m.S("\0\255"), m.P"\0" + "\255") -- charset extremes + +local word = alpha^1 * (1 - alpha)^0 assert((word^0 * -1):match"alo alo") assert(m.match(word^1 * -1, "alo alo")) @@ -117,7 +132,7 @@ assert(not m.match(word^-1 * -1, "alo alo")) assert(m.match(word^-2 * -1, "alo alo")) assert(m.match(word^-3 * -1, "alo alo")) -eos = m.P(-1) +local eos = m.P(-1) assert(m.match(digit^0 * letter * digit * eos, "1298a1")) assert(not m.match(digit^0 * letter * eos, "1257a1")) @@ -154,8 +169,8 @@ assert(m.match( basiclookfor((#m.P(b) * 1) * m.Cp()), " ( (a)") == 7) a = {m.match(m.C(digit^1 * m.Cc"d") + m.C(letter^1 * m.Cc"l"), "123")} checkeq(a, {"123", "d"}) -a = {m.match(m.C(digit^1) * "d" * -1 + m.C(letter^1 * m.Cc"l"), "123d")} -checkeq(a, {"123"}) +-- bug in LPeg 0.12 (nil value does not create a 'ktable') +assert(m.match(m.Cc(nil), "") == nil) a = {m.match(m.C(digit^1 * m.Cc"d") + m.C(letter^1 * m.Cc"l"), "abcd")} checkeq(a, {"abcd", "l"}) @@ -178,6 +193,24 @@ checkeq(a, {1, 5}) t = {m.match({[1] = m.C(m.C(1) * m.V(1) + -1)}, "abc")} checkeq(t, {"abc", "a", "bc", "b", "c", "c", ""}) +-- bug in 0.12 ('hascapture' did not check for captures inside a rule) +do + local pat = m.P{ + 'S'; + S1 = m.C('abc') + 3, + S = #m.V('S1') -- rule has capture, but '#' must ignore it + } + assert(pat:match'abc' == 1) +end + + +-- bug: loop in 'hascaptures' +do + local p = m.C(-m.P{m.P'x' * m.V(1) + m.P'y'}) + assert(p:match("xxx") == "") +end + + -- test for small capture boundary for i = 250,260 do @@ -185,9 +218,8 @@ for i = 250,260 do assert(#m.match(m.C(m.C(i)), string.rep('a', i)) == i) end - --- tests for any*n -for n = 1, 550 do +-- tests for any*n and any*-n +for n = 1, 550, 13 do local x_1 = string.rep('x', n - 1) local x = x_1 .. 'a' assert(not m.P(n):match(x_1)) @@ -202,9 +234,11 @@ for n = 1, 550 do assert(m.match(n3 * m.Cp() * n3 * n3, x) == n3 + 1) end +-- true values assert(m.P(0):match("x") == 1) assert(m.P(0):match("") == 1) assert(m.C(0):match("x") == "") + assert(m.match(m.Cc(0) * m.P(10) + m.Cc(1) * "xuxu", "xuxu") == 1) assert(m.match(m.Cc(0) * m.P(10) + m.Cc(1) * "xuxu", "xuxuxuxuxu") == 0) assert(m.match(m.C(m.P(2)^1), "abcde") == "abcd") @@ -243,6 +277,35 @@ assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x", "eeex") == 4) assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x", "cd") == 3) assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x", "x") == 2) assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x" + "", "zee") == 1) +assert(not m.match(("aa" * m.P"bc"^-1 + "aab") * "e", "aabe")) + +assert(m.match("alo" * (m.P"\n" + -1), "alo") == 4) + + +-- bug in 0.12 (rc1) +assert(m.match((m.P"\128\187\191" + m.S"abc")^0, "\128\187\191") == 4) + +assert(m.match(m.S"\0\128\255\127"^0, string.rep("\0\128\255\127", 10)) == + 4*10 + 1) + +-- optimizations with optional parts +assert(m.match(("ab" * -m.P"c")^-1, "abc") == 1) +assert(m.match(("ab" * #m.P"c")^-1, "abd") == 1) +assert(m.match(("ab" * m.B"c")^-1, "ab") == 1) +assert(m.match(("ab" * m.P"cd"^0)^-1, "abcdcdc") == 7) + +assert(m.match(m.P"ab"^-1 - "c", "abcd") == 3) + +p = ('Aa' * ('Bb' * ('Cc' * m.P'Dd'^0)^0)^0)^-1 +assert(p:match("AaBbCcDdBbCcDdDdDdBb") == 21) + + +-- bug in 0.12.2 +-- p = { ('ab' ('c' 'ef'?)*)? } +p = m.C(('ab' * ('c' * m.P'ef'^-1)^0)^-1) +s = "abcefccefc" +assert(s == p:match(s)) + pi = "3.14159 26535 89793 23846 26433 83279 50288 41971 69399 37510" assert(m.match(m.Cs((m.P"1" / "a" + m.P"5" / "b" + m.P"9" / "c" + 1)^0), pi) == @@ -255,6 +318,17 @@ assert(m.match((m.P(3) + 4 * m.Cp()) * "a", "abca") == 5) t = {m.match(((m.P"a" + m.Cp()) * m.P"x")^0, "axxaxx")} checkeq(t, {3, 6}) + +-- tests for numbered captures +p = m.C(1) +assert(m.match(m.C(m.C(p * m.C(2)) * m.C(3)) / 3, "abcdefgh") == "a") +assert(m.match(m.C(m.C(p * m.C(2)) * m.C(3)) / 1, "abcdefgh") == "abcdef") +assert(m.match(m.C(m.C(p * m.C(2)) * m.C(3)) / 4, "abcdefgh") == "bc") +assert(m.match(m.C(m.C(p * m.C(2)) * m.C(3)) / 0, "abcdefgh") == 7) + +a, b, c = m.match(p * (m.C(p * m.C(2)) * m.C(3) / 4) * p, "abcdefgh") +assert(a == "a" and b == "efg" and c == "h") + -- test for table captures t = m.match(m.Ct(letter^1), "alo") checkeq(t, {}) @@ -286,52 +360,113 @@ p = m.Cg(m.Cg(m.Cg(m.C(1))^0) * m.Cg(m.Cc(1) * m.Cc(2))) t = {p:match'abc'} checkeq(t, {'a', 'b', 'c', 1, 2}) --- test for non-pattern as arguments to pattern functions +p = m.Ct(m.Cg(m.Cc(10), "hi") * m.C(1)^0 * m.Cg(m.Cc(20), "ho")) +t = p:match'' +checkeq(t, {hi = 10, ho = 20}) +t = p:match'abc' +checkeq(t, {hi = 10, ho = 20, 'a', 'b', 'c'}) -p = { ('a' * m.V(1))^-1 } * m.P'b' * { 'a' * m.V(2); m.V(1)^-1 } -assert(m.match(p, "aaabaac") == 7) +-- non-string group names +p = m.Ct(m.Cg(1, print) * m.Cg(1, 23.5) * m.Cg(1, io)) +t = p:match('abcdefghij') +assert(t[print] == 'a' and t[23.5] == 'b' and t[io] == 'c') --- a large table capture -t = m.match(m.Ct(m.C('a')^0), string.rep("a", 10000)) -assert(#t == 10000 and t[1] == 'a' and t[#t] == 'a') + +-- test for error messages +local function checkerr (msg, f, ...) + local st, err = pcall(f, ...) + assert(not st and m.match({ m.P(msg) + 1 * m.V(1) }, err)) +end + +checkerr("rule '1' may be left recursive", m.match, { m.V(1) * 'a' }, "a") +checkerr("rule '1' used outside a grammar", m.match, m.V(1), "") +checkerr("rule 'hiii' used outside a grammar", m.match, m.V('hiii'), "") +checkerr("rule 'hiii' undefined in given grammar", m.match, { m.V('hiii') }, "") +checkerr("undefined in given grammar", m.match, { m.V{} }, "") + +checkerr("rule 'A' is not a pattern", m.P, { m.P(1), A = {} }) +checkerr("grammar has no initial rule", m.P, { [print] = {} }) + +-- grammar with a long call chain before left recursion +p = {'a', + a = m.V'b' * m.V'c' * m.V'd' * m.V'a', + b = m.V'c', + c = m.V'd', + d = m.V'e', + e = m.V'f', + f = m.V'g', + g = m.P'' +} +checkerr("rule 'a' may be left recursive", m.match, p, "a") + +-- Bug in peephole optimization of LPeg 0.12 (IJmp -> ICommit) +-- the next grammar has an original sequence IJmp -> ICommit -> IJmp L1 +-- that is optimized to ICommit L1 + +p = m.P { (m.P {m.P'abc'} + 'ayz') * m.V'y'; y = m.P'x' } +assert(p:match('abcx') == 5 and p:match('ayzx') == 5 and not p:match'abc') + + +do + print "testing large dynamic Cc" + local lim = 2^16 - 1 + local c = 0 + local function seq (n) + if n == 1 then c = c + 1; return m.Cc(c) + else + local m = math.floor(n / 2) + return seq(m) * seq(n - m) + end + end + p = m.Ct(seq(lim)) + t = p:match('') + assert(t[lim] == lim) + checkerr("too many", function () p = p / print end) + checkerr("too many", seq, lim + 1) +end --- test for errors -local function checkerr (msg, ...) - assert(m.match({ m.P(msg) + 1 * m.V(1) }, select(2, pcall(...)))) +do + -- nesting of captures too deep + local p = m.C(1) + for i = 1, 300 do + p = m.Ct(p) + end + checkerr("too deep", p.match, p, "x") end -checkerr("rule '1' is left recursive", m.match, { m.V(1) * 'a' }, "a") -checkerr("stack overflow", m.match, m.C('a')^0, string.rep("a", 50000)) -checkerr("rule '1' outside a grammar", m.match, m.V(1), "") -checkerr("rule 'hiii' outside a grammar", m.match, m.V('hiii'), "") -checkerr("rule 'hiii' is not defined", m.match, { m.V('hiii') }, "") --- test for non-pattern as arguments to pattern functions +-- tests for non-pattern as arguments to pattern functions p = { ('a' * m.V(1))^-1 } * m.P'b' * { 'a' * m.V(2); m.V(1)^-1 } assert(m.match(p, "aaabaac") == 7) --- a large table capture -t = m.match(m.Ct(m.C('a')^0), string.rep("a", 10000)) -assert(#t == 10000 and t[1] == 'a' and t[#t] == 'a') +p = m.P'abc' * 2 * -5 * true * 'de' -- mix of numbers and strings and booleans +assert(p:match("abc01de") == 8) +assert(p:match("abc01de3456") == nil) --- test for errors -local function checkerr (msg, ...) - assert(m.match({ m.P(msg) + 1 * m.V(1) }, select(2, pcall(...)))) -end +p = 'abc' * (2 * (-5 * (true * m.P'de'))) + +assert(p:match("abc01de") == 8) +assert(p:match("abc01de3456") == nil) + +p = { m.V(2), m.P"abc" } * + (m.P{ "xx", xx = m.P"xx" } + { "x", x = m.P"a" * m.V"x" + "" }) +assert(p:match("abcaaaxx") == 7) +assert(p:match("abcxx") == 6) -checkerr("rule '1' is left recursive", m.match, { m.V(1) * 'a' }, "a") -checkerr("stack overflow", m.match, m.C('a')^0, string.rep("a", 50000)) -checkerr("rule '1' outside a grammar", m.match, m.V(1), "") -checkerr("rule 'hiii' outside a grammar", m.match, m.V('hiii'), "") -checkerr("rule 'hiii' is not defined", m.match, { m.V('hiii') }, "") -checkerr("rule is not defined", m.match, { m.V({}) }, "") + +-- a large table capture +t = m.match(m.Ct(m.C('a')^0), string.rep("a", 10000)) +assert(#t == 10000 and t[1] == 'a' and t[#t] == 'a') print('+') +-- bug in 0.10 (rechecking a grammar, after tail-call optimization) +m.P{ m.P { (m.P(3) + "xuxu")^0 * m.V"xuxu", xuxu = m.P(1) } } + local V = m.V local Space = m.S(" \n\t")^0 @@ -358,8 +493,8 @@ local function f_term (v1, op, v2, d) end G = m.P{ "Exp", - Exp = m.Cf(V"Factor" * m.Cg(FactorOp * V"Factor")^0, f_factor); - Factor = m.Cf(V"Term" * m.Cg(TermOp * V"Term")^0, f_term); + Exp = V"Factor" * (FactorOp * V"Factor" % f_factor)^0; + Factor = V"Term" * (TermOp * V"Term" % f_term)^0; Term = Number / tonumber + Open * V"Exp" * Close; } @@ -403,11 +538,84 @@ assert(m.match(m.Cs((- -m.P("a") * 1 + m.P(1)/".")^0), "aloal") == "a..a.") assert(m.match(m.Cs((-((-m.P"a")/"") * 1 + m.P(1)/".")^0), "aloal") == "a..a.") +-- fixed length +do + -- 'and' predicate using fixed length + local p = m.C(#("a" * (m.P("bd") + "cd")) * 2) + assert(p:match("acd") == "ac") + + p = #m.P{ "a" * m.V(2), m.P"b" } * 2 + assert(p:match("abc") == 3) + + p = #(m.P"abc" * m.B"c") + assert(p:match("abc") == 1 and not p:match("ab")) + + p = m.P{ "a" * m.V(2), m.P"b"^1 } + checkerr("pattern may not have fixed length", m.B, p) + + p = "abc" * (m.P"b"^1 + m.P"a"^0) + checkerr("pattern may not have fixed length", m.B, p) +end + + +p = -m.P'a' * m.Cc(1) + -m.P'b' * m.Cc(2) + -m.P'c' * m.Cc(3) +assert(p:match('a') == 2 and p:match('') == 1 and p:match('b') == 1) + +p = -m.P'a' * m.Cc(10) + #m.P'a' * m.Cc(20) +assert(p:match('a') == 20 and p:match('') == 10 and p:match('b') == 10) + + + +-- look-behind predicate +assert(not m.match(m.B'a', 'a')) +assert(m.match(1 * m.B'a', 'a') == 2) +assert(not m.match(m.B(1), 'a')) +assert(m.match(1 * m.B(1), 'a') == 2) +assert(m.match(-m.B(1), 'a') == 1) +assert(m.match(m.B(250), string.rep('a', 250)) == nil) +assert(m.match(250 * m.B(250), string.rep('a', 250)) == 251) + +-- look-behind with an open call +checkerr("pattern may not have fixed length", m.B, m.V'S1') +checkerr("too long to look behind", m.B, 260) + +B = #letter * -m.B(letter) + -letter * m.B(letter) +x = m.Ct({ (B * m.Cp())^-1 * (1 * m.V(1) + m.P(true)) }) +checkeq(m.match(x, 'ar cal c'), {1,3,4,7,9,10}) +checkeq(m.match(x, ' ar cal '), {2,4,5,8}) +checkeq(m.match(x, ' '), {}) +checkeq(m.match(x, 'aloalo'), {1,7}) + +assert(m.match(B, "a") == 1) +assert(m.match(1 * B, "a") == 2) +assert(not m.B(1 - letter):match("")) +assert((-m.B(letter)):match("") == 1) + +assert((4 * m.B(letter, 4)):match("aaaaaaaa") == 5) +assert(not (4 * m.B(#letter * 5)):match("aaaaaaaa")) +assert((4 * -m.B(#letter * 5)):match("aaaaaaaa") == 5) + +-- look-behind with grammars +assert(m.match('a' * m.B{'x', x = m.P(3)}, 'aaa') == nil) +assert(m.match('aa' * m.B{'x', x = m.P('aaa')}, 'aaaa') == nil) +assert(m.match('aaa' * m.B{'x', x = m.P('aaa')}, 'aaaaa') == 4) + + + +-- bug in 0.9 +assert(m.match(('a' * #m.P'b'), "ab") == 2) +assert(not m.match(('a' * #m.P'b'), "a")) + +assert(not m.match(#m.S'567', "")) +assert(m.match(#m.S'567' * 1, "6") == 2) + -- tests for Tail Calls +p = m.P{ 'a' * m.V(1) + '' } +assert(p:match(string.rep('a', 1000)) == 1001) + -- create a grammar for a simple DFA for even number of 0s and 1s --- finished in '$': -- -- ->1 <---0---> 2 -- ^ ^ @@ -420,18 +628,34 @@ assert(m.match(m.Cs((-((-m.P"a")/"") * 1 + m.P(1)/".")^0), "aloal") == "a..a.") -- this grammar should keep no backtracking information p = m.P{ - [1] = '0' * m.V(2) + '1' * m.V(3) + '$', + [1] = '0' * m.V(2) + '1' * m.V(3) + -1, [2] = '0' * m.V(1) + '1' * m.V(4), [3] = '0' * m.V(4) + '1' * m.V(1), [4] = '0' * m.V(3) + '1' * m.V(2), } -assert(p:match(string.rep("00", 10000) .. "$")) -assert(p:match(string.rep("01", 10000) .. "$")) -assert(p:match(string.rep("011", 10000) .. "$")) -assert(not p:match(string.rep("011", 10001) .. "$")) +assert(p:match(string.rep("00", 10000))) +assert(p:match(string.rep("01", 10000))) +assert(p:match(string.rep("011", 10000))) +assert(not p:match(string.rep("011", 10000) .. "1")) +assert(not p:match(string.rep("011", 10001))) + + +-- this grammar does need backtracking info. +local lim = 10000 +p = m.P{ '0' * m.V(1) + '0' } +checkerr("stack overflow", m.match, p, string.rep("0", lim)) +m.setmaxstack(2*lim) +checkerr("stack overflow", m.match, p, string.rep("0", lim)) +m.setmaxstack(2*lim + 4) +assert(m.match(p, string.rep("0", lim)) == lim + 1) +-- this repetition should not need stack space (only the call does) +p = m.P{ ('a' * m.V(1))^0 * 'b' + 'c' } +m.setmaxstack(200) +assert(p:match(string.rep('a', 180) .. 'c' .. string.rep('b', 180)) == 362) +m.setmaxstack(100) -- restore low limit -- tests for optional start position assert(m.match("a", "abc", 1)) @@ -449,21 +673,14 @@ assert(not m.match(1, "", 1)) assert(not m.match(1, "", -1)) assert(not m.match(1, "", 0)) - --- basic tests for external C function - -assert(m.match(m.span("abcd"), "abbbacebb") == 7) -assert(m.match(m.span("abcd"), "0abbbacebb") == 1) -assert(m.match(m.span("abcd"), "") == 1) - print("+") -- tests for argument captures -assert(not pcall(m.Carg, 0)) -assert(not pcall(m.Carg, -1)) -assert(not pcall(m.Carg, 2^18)) -assert(not pcall(m.match, m.Carg(1), 'a', 1)) +checkerr("invalid argument", m.Carg, 0) +checkerr("invalid argument", m.Carg, -1) +checkerr("invalid argument", m.Carg, 2^18) +checkerr("absent extra argument #1", m.match, m.Carg(1), 'a', 1) assert(m.match(m.Carg(1), 'a', 1, print) == print) x = {m.match(m.Carg(1) * m.Carg(2), '', 1, 10, 20)} checkeq(x, {10, 20}) @@ -484,7 +701,7 @@ assert(m.match(m.Cmt(m.Cg(m.Carg(3), "a") * t = {} s = "" -p = function (s1, i) assert(s == s1); t[#t + 1] = i; return nil end +p = m.P(function (s1, i) assert(s == s1); t[#t + 1] = i; return nil end) * false s = "hi, this is a test" assert(m.match(((p - m.P(-1)) + 2)^0, s) == string.len(s) + 1) assert(#t == string.len(s)/2 and t[1] == 1 and t[2] == 3) @@ -506,7 +723,7 @@ assert(#t == string.len(s) and t[1] == 2 and t[2] == 3) t = {} p = m.P(function (s1, i) assert(s == s1); t[#t + 1] = i; - return i <= s1:len() and i + 1 end) + return i <= s1:len() and i end) * 1 s = "hi, this is a test" assert(m.match(p^0, s) == string.len(s) + 1) assert(#t == string.len(s) + 1 and t[1] == 1 and t[2] == 2) @@ -516,14 +733,16 @@ assert(m.match(p, "aaaa") == 5) assert(m.match(p, "abaa") == 2) assert(not m.match(p, "baaa")) -assert(not pcall(m.match, function () return 2^20 end, s)) -assert(not pcall(m.match, function () return 0 end, s)) -assert(not pcall(m.match, function (s, i) return i - 1 end, s)) -assert(not pcall(m.match, m.P(1)^0 * function (_, i) return i - 1 end, s)) +checkerr("invalid position", m.match, function () return 2^20 end, s) +checkerr("invalid position", m.match, function () return 0 end, s) +checkerr("invalid position", m.match, function (s, i) return i - 1 end, s) +checkerr("invalid position", m.match, + m.P(1)^0 * function (_, i) return i - 1 end, s) assert(m.match(m.P(1)^0 * function (_, i) return i end * -1, s)) -assert(not pcall(m.match, m.P(1)^0 * function (_, i) return i + 1 end, s)) +checkerr("invalid position", m.match, + m.P(1)^0 * function (_, i) return i + 1 end, s) assert(m.match(m.P(function (s, i) return s:len() + 1 end) * -1, s)) -assert(not pcall(m.match, m.P(function (s, i) return s:len() + 2 end) * -1, s)) +checkerr("invalid position", m.match, m.P(function (s, i) return s:len() + 2 end) * -1, s) assert(not m.match(m.P(function (s, i) return s:len() end) * -1, s)) assert(m.match(m.P(1)^0 * function (_, i) return true end, s) == string.len(s) + 1) @@ -531,8 +750,8 @@ for i = 1, string.len(s) + 1 do assert(m.match(function (_, _) return i end, s) == i) end -p = (m.P(function (s, i) return i%2 == 0 and i + 1 end) - + m.P(function (s, i) return i%2 ~= 0 and i + 2 <= s:len() and i + 3 end))^0 +p = (m.P(function (s, i) return i%2 == 0 and i end) * 1 + + m.P(function (s, i) return i%2 ~= 0 and i + 2 <= s:len() and i end) * 3)^0 * -1 assert(p:match(string.rep('a', 14000))) @@ -568,6 +787,10 @@ t = {m.match(m.Cc(nil,nil,4) * m.Cc(nil,3) * m.Cc(nil, nil) / g / g, "")} t1 = {1,1,nil,nil,4,nil,3,nil,nil} for i=1,10 do assert(t[i] == t1[i]) end +-- bug in 0.12.2: ktable with only nil could be eliminated when joining +-- with a pattern without ktable +assert((m.P"aaa" * m.Cc(nil)):match"aaa" == nil) + t = {m.match((m.C(1) / function (x) return x, x.."x" end)^0, "abc")} checkeq(t, {"a", "ax", "b", "bx", "c", "cx"}) @@ -606,9 +829,9 @@ assert(m.match(m.Cs((m.P(1) / ".xx")^0), "abcd") == ".xx.xx.xx.xx") assert(m.match(m.Cp() * m.P(3) * m.Cp()/"%2%1%1 - %0 ", "abcde") == "411 - abc ") -assert(pcall(m.match, m.P(1)/"%0", "abc")) -assert(not pcall(m.match, m.P(1)/"%1", "abc")) -- out of range -assert(not pcall(m.match, m.P(1)/"%9", "abc")) -- out of range +assert(m.match(m.P(1)/"%0", "abc") == "a") +checkerr("invalid capture index", m.match, m.P(1)/"%1", "abc") +checkerr("invalid capture index", m.match, m.P(1)/"%9", "abc") p = m.C(1) p = p * p; p = p * p; p = p * p * m.C(1) / "%9 - %1" @@ -626,7 +849,7 @@ assert(m.match(m.C(1)^0 / "%9-%1-%0-%3", s) == "9-1-" .. s .. "-3") p = m.Cc('alo') * m.C(1) / "%1 - %2 - %1" assert(p:match'x' == 'alo - x - alo') -assert(not pcall(m.match, m.Cc(true) / "%1", "a")) +checkerr("invalid capture value (a boolean)", m.match, m.Cc(true) / "%1", "a") -- long strings for string capture l = 10000 @@ -643,6 +866,7 @@ print"+" -- accumulator capture function f (x) return x + 1 end assert(m.match(m.Cf(m.Cc(0) * m.C(1)^0, f), "alo alo") == 7) +assert(m.match(m.Cc(0) * (m.C(1) % f)^0, "alo alo") == 7) t = {m.match(m.Cf(m.Cc(1,2,3), error), "")} checkeq(t, {1}) @@ -650,29 +874,47 @@ p = m.Cf(m.Ct(true) * m.Cg(m.C(m.R"az"^1) * "=" * m.C(m.R"az"^1) * ";")^0, rawset) t = p:match("a=b;c=du;xux=yuy;") checkeq(t, {a="b", c="du", xux="yuy"}) - + + +-- errors in fold capture + +-- no initial capture +checkerr("no initial value", m.match, m.Cf(m.P(5), print), 'aaaaaa') +-- no initial capture (very long match forces fold to be a pair open-close) +checkerr("no initial value", m.match, m.Cf(m.P(500), print), + string.rep('a', 600)) + + +-- errors in accumulator capture + +-- no initial capture +checkerr("no previous value", m.match, m.P(5) % print, 'aaaaaa') +-- no initial capture (very long match forces fold to be a pair open-close) +checkerr("no previous value", m.match, m.P(500) % print, + string.rep('a', 600)) + -- tests for loop checker -local function haveloop (p) - assert(not pcall(function (p) return p^0 end, m.P(p))) +local function isnullable (p) + checkerr("may accept empty string", function (p) return p^0 end, m.P(p)) end -haveloop(m.P("x")^-4) +isnullable(m.P("x")^-4) assert(m.match(((m.P(0) + 1) * m.S"al")^0, "alo") == 3) assert(m.match((("x" + #m.P(1))^-4 * m.S"al")^0, "alo") == 3) -haveloop("") -haveloop(m.P("x")^0) -haveloop(m.P("x")^-1) -haveloop(m.P("x") + 1 + 2 + m.P("a")^-1) -haveloop(-m.P("ab")) -haveloop(- -m.P("ab")) -haveloop(# #(m.P("ab") + "xy")) -haveloop(- #m.P("ab")^0) -haveloop(# -m.P("ab")^1) -haveloop(#m.V(3)) -haveloop(m.V(3) + m.V(1) + m.P('a')^-1) -haveloop({[1] = m.V(2) * m.V(3), [2] = m.V(3), [3] = m.P(0)}) +isnullable("") +isnullable(m.P("x")^0) +isnullable(m.P("x")^-1) +isnullable(m.P("x") + 1 + 2 + m.P("a")^-1) +isnullable(-m.P("ab")) +isnullable(- -m.P("ab")) +isnullable(# #(m.P("ab") + "xy")) +isnullable(- #m.P("ab")^0) +isnullable(# -m.P("ab")^1) +isnullable(#m.V(3)) +isnullable(m.V(3) + m.V(1) + m.P('a')^-1) +isnullable({[1] = m.V(2) * m.V(3), [2] = m.V(3), [3] = m.P(0)}) assert(m.match(m.P{[1] = m.V(2) * m.V(3), [2] = m.V(3), [3] = m.P(1)}^0, "abc") == 3) assert(m.match(m.P""^-3, "a") == 1) @@ -682,28 +924,45 @@ local function find (p, s) end -local function badgrammar (g, exp) - local err, msg = pcall(m.P, g) - assert(not err) - if exp then assert(find(exp, msg)) end +local function badgrammar (g, expected) + local stat, msg = pcall(m.P, g) + assert(not stat) + if expected then assert(find(expected, msg)) end end badgrammar({[1] = m.V(1)}, "rule '1'") badgrammar({[1] = m.V(2)}, "rule '2'") -- invalid non-terminal badgrammar({[1] = m.V"x"}, "rule 'x'") -- invalid non-terminal -badgrammar({[1] = m.V{}}, "rule ") -- invalid non-terminal -badgrammar({[1] = #m.P("a") * m.V(1)}, "rule '1'") -badgrammar({[1] = -m.P("a") * m.V(1)}, "rule '1'") -badgrammar({[1] = -1 * m.V(1)}, "rule '1'") -badgrammar({[1] = 1 * m.V(2), [2] = m.V(2)}, "rule '2'") -badgrammar({[1] = m.P(0), [2] = 1 * m.V(1)^0}, "loop in rule '2'") -badgrammar({ lpeg.V(2), lpeg.V(3)^0, lpeg.P"" }, "rule '2'") -badgrammar({ lpeg.V(2) * lpeg.V(3)^0, lpeg.V(3)^0, lpeg.P"" }, "rule '1'") -badgrammar({ #(lpeg.V(1) * 'a') }, "rule '1'") -badgrammar({ -(lpeg.V(1) * 'a') }, "rule '1'") - -assert(m.match({'a' * -lpeg.V(1)}, "aaa") == 2) -assert(m.match({'a' * -lpeg.V(1)}, "aaaa") == nil) +badgrammar({[1] = m.V{}}, "rule '(a table)'") -- invalid non-terminal +badgrammar({[1] = #m.P("a") * m.V(1)}, "rule '1'") -- left-recursive +badgrammar({[1] = -m.P("a") * m.V(1)}, "rule '1'") -- left-recursive +badgrammar({[1] = -1 * m.V(1)}, "rule '1'") -- left-recursive +badgrammar({[1] = -1 + m.V(1)}, "rule '1'") -- left-recursive +badgrammar({[1] = 1 * m.V(2), [2] = m.V(2)}, "rule '2'") -- left-recursive +badgrammar({[1] = 1 * m.V(2)^0, [2] = m.P(0)}, "rule '1'") -- inf. loop +badgrammar({ m.V(2), m.V(3)^0, m.P"" }, "rule '2'") -- inf. loop +badgrammar({ m.V(2) * m.V(3)^0, m.V(3)^0, m.P"" }, "rule '1'") -- inf. loop +badgrammar({"x", x = #(m.V(1) * 'a') }, "rule '1'") -- inf. loop +badgrammar({ -(m.V(1) * 'a') }, "rule '1'") -- inf. loop +badgrammar({"x", x = m.P'a'^-1 * m.V"x"}, "rule 'x'") -- left recursive +badgrammar({"x", x = m.P'a' * m.V"y"^1, y = #m.P(1)}, "rule 'x'") + +assert(m.match({'a' * -m.V(1)}, "aaa") == 2) +assert(m.match({'a' * -m.V(1)}, "aaaa") == nil) + + +-- good x bad grammars +m.P{ ('a' * m.V(1))^-1 } +m.P{ -('a' * m.V(1)) } +m.P{ ('abc' * m.V(1))^-1 } +m.P{ -('abc' * m.V(1)) } +badgrammar{ #m.P('abc') * m.V(1) } +badgrammar{ -('a' + m.V(1)) } +m.P{ #('a' * m.V(1)) } +badgrammar{ #('a' + m.V(1)) } +m.P{ m.B{ m.P'abc' } * 'a' * m.V(1) } +badgrammar{ m.B{ m.P'abc' } * m.V(1) } +badgrammar{ ('a' + m.P'bcd')^-1 * m.V(1) } -- simple tests for maximum sizes: @@ -735,18 +994,54 @@ for i = 1, 10 do assert(p:match("aaaaaaaaaaa") == 11 - i + 1) end -print"+" --- tests for back references -assert(not pcall(m.match, m.Cb('x'), '')) -assert(not pcall(m.match, m.Cg(1, 'a') * m.Cb('b'), 'a')) +print "testing back references" + +checkerr("back reference 'x' not found", m.match, m.Cb('x'), '') +checkerr("back reference 'b' not found", m.match, m.Cg(1, 'a') * m.Cb('b'), 'a') p = m.Cg(m.C(1) * m.C(1), "k") * m.Ct(m.Cb("k")) t = p:match("ab") checkeq(t, {"a", "b"}) +do + -- some basic cases + assert(m.match(m.Cg(m.Cc(3), "a") * m.Cb("a"), "a") == 3) + assert(m.match(m.Cg(m.C(1), 133) * m.Cb(133), "X") == "X") + + -- first reference to 'x' should not see the group enclosing it + local p = m.Cg(m.Cb('x'), 'x') * m.Cb('x') + checkerr("back reference 'x' not found", m.match, p, '') + + local p = m.Cg(m.Cb('x') * m.C(1), 'x') * m.Cb('x') + checkerr("back reference 'x' not found", m.match, p, 'abc') + + -- reference to 'x' should not see the group enclosed in another capture + local s = string.rep("a", 30) + local p = (m.C(1)^-4 * m.Cg(m.C(1), 'x')) / {} * m.Cb('x') + checkerr("back reference 'x' not found", m.match, p, s) + + local p = (m.C(1)^-20 * m.Cg(m.C(1), 'x')) / {} * m.Cb('x') + checkerr("back reference 'x' not found", m.match, p, s) + + -- second reference 'k' should refer to 10 and first ref. 'k' + p = m.Cg(m.Cc(20), 'k') * m.Cg(m.Cc(10) * m.Cb('k') * m.C(1), 'k') + * (m.Cb('k') / function (a,b,c) return a*10 + b + tonumber(c) end) + -- 10 * 10 (Cc) + 20 (Cb) + 7 (C) == 127 + assert(p:match("756") == 127) + +end + +p = m.P(true) +for i = 1, 10 do p = p * m.Cg(1, i) end +for i = 1, 10 do + local p = p * m.Cb(i) + assert(p:match('abcdefghij') == string.sub('abcdefghij', i, i)) +end + + t = {} function foo (p) t[#t + 1] = p; return p .. "x" end @@ -765,10 +1060,27 @@ checkeq(t, {'ab', -- tests for match-time captures +p = m.P'a' * (function (s, i) return (s:sub(i, i) == 'b') and i + 1 end) + + 'acd' + +assert(p:match('abc') == 3) +assert(p:match('acd') == 4) + local function id (s, i, ...) return true, ... end +do -- run-time capture in an end predicate (should discard its value) + local x = 0 + function foo (s, i) + x = x + 1 + return true, x + end + + local p = #(m.Cmt("", foo) * "xx") * m.Cmt("", foo) + assert(p:match("xx") == 2) +end + assert(m.Cmt(m.Cs((m.Cmt(m.S'abc' / { a = 'x', c = 'y' }, id) + m.R'09'^1 / string.char + m.P(1))^0), id):match"acb98+68c" == "xyb\98+\68y") @@ -783,15 +1095,14 @@ checkeq(x, {'a', 'g', {}, {{'b'}, 'c'}, {'d', {'e'}}}); x = {(m.Cmt(1, id)^0):match(string.rep('a', 500))} assert(#x == 500) -assert(not pcall(m.match, m.Cmt(1, id)^0, string.rep('a', 50000))) local function id(s, i, x) - if x == 'a' then return i + 1, 1, 3, 7 + if x == 'a' then return i, 1, 3, 7 else return nil, 2, 4, 6, 8 end end -p = ((m.P(id) + m.Cmt(2, id) + m.Cmt(1, id)))^0 +p = ((m.P(id) * 1 + m.Cmt(2, id) * 1 + m.Cmt(1, id) * 1))^0 assert(table.concat{p:match('abababab')} == string.rep('137', 4)) local function ref (s, i, x) @@ -817,29 +1128,193 @@ p = (any - p)^0 * p * any^0 * -1 assert(p:match'abbbc-bc ddaa' == 'BC') +do -- match-time captures cannot be optimized away + local touch = 0 + f = m.P(function () touch = touch + 1; return true end) + + local function check(n) n = n or 1; assert(touch == n); touch = 0 end + + assert(m.match(f * false + 'b', 'a') == nil); check() + assert(m.match(f * false + 'b', '') == nil); check() + assert(m.match( (f * 'a')^0 * 'b', 'b') == 2); check() + assert(m.match( (f * 'a')^0 * 'b', '') == nil); check() + assert(m.match( (f * 'a')^-1 * 'b', 'b') == 2); check() + assert(m.match( (f * 'a')^-1 * 'b', '') == nil); check() + assert(m.match( ('b' + f * 'a')^-1 * 'b', '') == nil); check() + assert(m.match( (m.P'b'^-1 * f * 'a')^-1 * 'b', '') == nil); check() + assert(m.match( (-m.P(1) * m.P'b'^-1 * f * 'a')^-1 * 'b', '') == nil); + check() + assert(m.match( (f * 'a' + 'b')^-1 * 'b', '') == nil); check() + assert(m.match(f * 'a' + f * 'b', 'b') == 2); check(2) + assert(m.match(f * 'a' + f * 'b', 'a') == 2); check(1) + assert(m.match(-f * 'a' + 'b', 'b') == 2); check(1) + assert(m.match(-f * 'a' + 'b', '') == nil); check(1) +end c = '[' * m.Cg(m.P'='^0, "init") * '[' * { m.Cmt(']' * m.C(m.P'='^0) * ']' * m.Cb("init"), function (_, _, s1, s2) return s1 == s2 end) - + 1 * m.V(1) } / function () end + + 1 * m.V(1) } / 0 assert(c:match'[==[]]====]]]]==]===[]' == 18) assert(c:match'[[]=]====]=]]]==]===[]' == 14) assert(not c:match'[[]=]====]=]=]==]===[]') +-- old bug: optimization of concat with fail removed match-time capture +p = m.Cmt(0, function (s) p = s end) * m.P(false) +assert(not p:match('alo')) +assert(p == 'alo') + + +-- ensure that failed match-time captures are not kept on Lua stack +do + local t = {__mode = "kv"}; setmetatable(t,t) + local c = 0 + + local function foo (s,i) + collectgarbage(); + assert(next(t) == "__mode" and next(t, "__mode") == nil) + local x = {} + t[x] = true + c = c + 1 + return i, x + end + + local p = m.P{ m.Cmt(0, foo) * m.P(false) + m.P(1) * m.V(1) + m.P"" } + p:match(string.rep('1', 10)) + assert(c == 11) +end + + +-- Return a match-time capture that returns 'n' captures +local function manyCmt (n) + return m.Cmt("a", function () + local a = {}; for i = 1, n do a[i] = n - i end + return true, unpack(a) + end) +end + +-- bug in 1.0: failed match-time that used previous match-time results +do + local x + local function aux (...) x = #{...}; return false end + local res = {m.match(m.Cmt(manyCmt(20), aux) + manyCmt(10), "a")} + assert(#res == 10 and res[1] == 9 and res[10] == 0) +end + + +-- bug in 1.0: problems with math-times returning too many captures +if _VERSION >= "Lua 5.2" then + local lim = 2^11 - 10 + local res = {m.match(manyCmt(lim), "a")} + assert(#res == lim and res[1] == lim - 1 and res[lim] == 0) + checkerr("too many", m.match, manyCmt(2^15), "a") +end + +p = (m.P(function () return true, "a" end) * 'a' + + m.P(function (s, i) return i, "aa", 20 end) * 'b' + + m.P(function (s,i) if i <= #s then return i, "aaa" end end) * 1)^0 + +t = {p:match('abacc')} +checkeq(t, {'a', 'aa', 20, 'a', 'aaa', 'aaa'}) + + +do print"testing large grammars" + local lim = 1000 -- number of rules + local t = {} + + for i = 3, lim do + t[i] = m.V(i - 1) -- each rule calls previous one + end + t[1] = m.V(lim) -- start on last rule + t[2] = m.C("alo") -- final rule + + local P = m.P(t) -- build grammar + assert(P:match("alo") == "alo") + + t[#t + 1] = m.P("x") -- one more rule... + checkerr("too many rules", m.P, t) +end + + +print "testing UTF-8 ranges" + +do -- a few typical UTF-8 ranges + local p = m.utfR(0x410, 0x44f)^1 / "cyr: %0" + + m.utfR(0x4e00, 0x9fff)^1 / "cjk: %0" + + m.utfR(0x1F600, 0x1F64F)^1 / "emot: %0" + + m.utfR(0, 0x7f)^1 / "ascii: %0" + + m.utfR(0, 0x10ffff) / "other: %0" + + p = m.Ct(p^0) * -m.P(1) + + local cyr = "ждюя" + local emot = "\240\159\152\128\240\159\153\128" -- 😀🙀 + local cjk = "专举乸" + local ascii = "alo" + local last = "\244\143\191\191" -- U+10FFFF + + local s = cyr .. "—" .. emot .. "—" .. cjk .. "—" .. ascii .. last + t = (p:match(s)) + + assert(t[1] == "cyr: " .. cyr and t[2] == "other: —" and + t[3] == "emot: " .. emot and t[4] == "other: —" and + t[5] == "cjk: " .. cjk and t[6] == "other: —" and + t[7] == "ascii: " .. ascii and t[8] == "other: " .. last and + t[9] == nil) + + -- failing UTF-8 matches and borders + assert(not m.match(m.utfR(10, 0x2000), "\9")) + assert(not m.match(m.utfR(10, 0x2000), "\226\128\129")) + assert(m.match(m.utfR(10, 0x2000), "\10") == 2) + assert(m.match(m.utfR(10, 0x2000), "\226\128\128") == 4) +end + + +do -- valid and invalid code points + local p = m.utfR(0, 0x10ffff)^0 + assert(p:match("汉字\128") == #"汉字" + 1) + assert(p:match("\244\159\191") == 1) + assert(p:match("\244\159\191\191") == 1) + assert(p:match("\255") == 1) + + -- basic errors + checkerr("empty range", m.utfR, 1, 0) + checkerr("invalid code point", m.utfR, 1, 0x10ffff + 1) +end + + +do -- back references (fixed width) + -- match a byte after a CJK point + local p = m.B(m.utfR(0x4e00, 0x9fff)) * m.C(1) + p = m.P{ p + m.P(1) * m.V(1) } -- search for 'p' + assert(p:match("ab д 专X x") == "X") + + -- match a byte after a hebrew point + local p = m.B(m.utfR(0x5d0, 0x5ea)) * m.C(1) + p = m.P(#"ש") * p + assert(p:match("שX") == "X") + + checkerr("fixed length", m.B, m.utfR(0, 0x10ffff)) +end + + ------------------------------------------------------------------- -- Tests for 're' module ------------------------------------------------------------------- +print"testing 're' module" -require "re" +local re = require "re" local match, compile = re.match, re.compile + + assert(match("a", ".") == 2) assert(match("a", "''") == 1) -assert(match("", "!.") == 1) +assert(match("", " ! . ") == 1) assert(not match("a", " ! . ")) assert(match("abcde", " ( . . ) * ") == 5) assert(match("abbcde", " [a-c] +") == 5) @@ -850,42 +1325,45 @@ assert(match("abbc--", " [ac-] +") == 2) assert(match("abbc--", " [-acb] + ") == 7) assert(not match("abbcde", " [b-z] + ")) assert(match("abb\"de", '"abb"["]"de"') == 7) -assert(match("abceeef", "'ac'? 'ab'* 'c' {'e'*} / 'abceeef' ") == "eee") +assert(match("abceeef", "'ac' ? 'ab' * 'c' { 'e' * } / 'abceeef' ") == "eee") assert(match("abceeef", "'ac'? 'ab'* 'c' { 'f'+ } / 'abceeef' ") == 8) -local t = {match("abceefe", "((&'e' {})? .)*")} + +assert(re.match("aaand", "[a]^2") == 3) + +local t = {match("abceefe", "( ( & 'e' {} ) ? . ) * ")} checkeq(t, {4, 5, 7}) local t = {match("abceefe", "((&&'e' {})? .)*")} checkeq(t, {4, 5, 7}) local t = {match("abceefe", "( ( ! ! 'e' {} ) ? . ) *")} checkeq(t, {4, 5, 7}) -local t = {match("abceefe", "((&!&!'e' {})? .)*")} +local t = {match("abceefe", "(( & ! & ! 'e' {})? .)*")} checkeq(t, {4, 5, 7}) assert(match("cccx" , "'ab'? ('ccc' / ('cde' / 'cd'*)? / 'ccc') 'x'+") == 5) assert(match("cdx" , "'ab'? ('ccc' / ('cde' / 'cd'*)? / 'ccc') 'x'+") == 4) assert(match("abcdcdx" , "'ab'? ('ccc' / ('cde' / 'cd'*)? / 'ccc') 'x'+") == 8) -assert(match("abc", "a <- (. )?") == 4) -b = "balanced <- '(' ([^()] / )* ')'" +assert(match("abc", "a <- (. a)?") == 4) +b = "balanced <- '(' ([^()] / balanced)* ')'" assert(match("(abc)", b)) assert(match("(a(b)((c) (d)))", b)) assert(not match("(a(b ((c) (d)))", b)) -b = compile[[ balanced <- "(" ([^()] / )* ")" ]] +b = compile[[ balanced <- "(" ([^()] / balanced)* ")" ]] assert(b == m.P(b)) assert(b:match"((((a))(b)))") local g = [[ - S <- "0" / "1" / "" -- balanced strings - A <- "0" / "1" -- one more 0 - B <- "1" / "0" -- one more 1 + S <- "0" B / "1" A / "" -- balanced strings + A <- "0" S / "1" A A -- one more 0 + B <- "1" S / "0" B B -- one more 1 ]] assert(match("00011011", g) == 9) local g = [[ - S <- ("0" / "1" )* - A <- "0" / "1" - B <- "1" / "0" + S <- ("0" B / "1" A)* + A <- "0" / "1" A A + B <- "1" / "0" B B ]] assert(match("00011011", g) == 9) assert(match("000110110", g) == 9) @@ -908,9 +1386,16 @@ assert(match("01234567890123456789", "[0-9]^3+") == 19) assert(match("01234567890123456789", "({....}{...}) -> '%2%1'") == "4560123") -t = match("0123456789", "{.}*->{}") +t = match("0123456789", "{| {.}* |}") checkeq(t, {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}) -assert(match("012345", "( (..) -> '%0%0' ) -> {}")[1] == "0101") +assert(match("012345", "{| (..) -> '%0%0' |}")[1] == "0101") + +assert(match("abcdef", "( {.} {.} {.} {.} {.} ) -> 3") == "c") +assert(match("abcdef", "( {:x: . :} {.} {.} {.} {.} ) -> 3") == "d") +assert(match("abcdef", "( {:x: . :} {.} {.} {.} {.} ) -> 0") == 6) + +assert(not match("abcdef", "{:x: ({.} {.} {.}) -> 2 :} =x")) +assert(match("abcbef", "{:x: ({.} {.} {.}) -> 2 :} =x")) eqcharset(compile"[]]", "]") eqcharset(compile"[][]", m.S"[]") @@ -931,28 +1416,41 @@ eqcharset(compile"[^-az]", any - m.S"a-z") eqcharset(compile"[^a-z]", any - m.R"az") eqcharset(compile"[^]['\"]", any - m.S[[]['"]]) +-- tests for comments in 're' +e = compile[[ +A <- _B -- \t \n %nl .<> <- -> -- +_B <- 'x' --]] +assert(e:match'xy' == 2) -- tests for 're' with pre-definitions -defs = {digits = m.R"09", letters = m.R"az"} +defs = {digits = m.R"09", letters = m.R"az", _=m.P"__"} e = compile("%letters (%letters / %digits)*", defs) assert(e:match"x123" == 5) +e = compile("%_", defs) +assert(e:match"__" == 3) e = compile([[ - S <- + - A <- %letters+ + S <- A+ + A <- %letters+ B B <- %digits+ ]], defs) e = compile("{[0-9]+'.'?[0-9]*} -> sin", math) assert(e:match("2.34") == math.sin(2.34)) +e = compile("'pi' -> math", _G) +assert(e:match("pi") == math.pi) + +e = compile("[ ]* 'version' -> _VERSION", _G) +assert(e:match(" version") == _VERSION) + function eq (_, _, a, b) return a == b end c = re.compile([[ - longstring <- '[' {:init: '='* :} '[' - close <- ']' =init ']' / . -]], {void = void}) + longstring <- '[' {:init: '='* :} '[' close + close <- ']' =init ']' / . close +]]) assert(c:match'[==[]]===]]]]==]===[]' == 17) assert(c:match'[[]=]====]=]]]==]===[]' == 14) @@ -967,10 +1465,22 @@ assert(not c:match'[[]=]====]=]=]==]===[]') assert(re.find("hi alalo", "{:x:..:} =x") == 4) assert(re.find("hi alalo", "{:x:..:} =x", 4) == 4) assert(not re.find("hi alalo", "{:x:..:} =x", 5)) -assert(re.find("hi alalo", "'al'", 5) == 6) +assert(re.find("hi alalo", "{'al'}", 5) == 6) assert(re.find("hi aloalolo", "{:x:..:} =x") == 8) assert(re.find("alo alohi x x", "{:word:%w+:}%W*(=word)!%w") == 11) +-- re.find discards any captures +local a,b,c = re.find("alo", "{.}{'o'}") +assert(a == 2 and b == 3 and c == nil) + +local function match (s,p) + local i,e = re.find(s,p) + if i then return s:sub(i, e) end +end +assert(match("alo alo", '[a-z]+') == "alo") +assert(match("alo alo", '{:x: [a-z]+ :} =x') == nil) +assert(match("alo alo", "{:x: [a-z]+ :} ' ' =x") == "alo alo") + assert(re.gsub("alo alo", "[abc]", "x") == "xlo xlo") assert(re.gsub("alo alo", "%w+", ".") == ". .") assert(re.gsub("hi, how are you", "[aeiou]", string.upper) == @@ -988,8 +1498,8 @@ assert(re.find("alo", "!.") == 4) function addtag (s, i, t, tag) t.tag = tag; return i, t end c = re.compile([[ - doc <- !. - block <- ( ( / { [^<]+ })* -> {} ?) => addtag + doc <- block !. + block <- (start {| (block / { [^<]+ })* |} end?) => addtag start <- '<' {:tag: [a-z]+ :} '>' end <- '' ]], {addtag = addtag}) @@ -999,41 +1509,65 @@ x = c:match[[ checkeq(x, {tag='x', 'hi', {tag = 'b', 'hello'}, 'but', {'totheend'}}) -assert(not pcall(compile, "x <- 'a' x <- 'b'")) -assert(not pcall(compile, "'x' -> x", {x = 3})) +-- test for folding captures +c = re.compile([[ + S <- (number (%s+ number)*) ~> add + number <- %d+ -> tonumber +]], {tonumber = tonumber, add = function (a,b) return a + b end}) +assert(c:match("3 401 50") == 3 + 401 + 50) + +-- test for accumulator captures +c = re.compile([[ + S <- number (%s+ number >> add)* + number <- %d+ -> tonumber +]], {tonumber = tonumber, add = function (a,b) return a + b end}) +assert(c:match("3 401 50") == 3 + 401 + 50) -- tests for look-ahead captures x = {re.match("alo", "&(&{.}) !{'b'} {&(...)} &{..} {...} {!.}")} -checkeq(x, {"a", "", "al", "alo", ""}) +checkeq(x, {"", "alo", ""}) -assert(re.match("aloalo", "{~ (((&{'al'}) -> 'A' / (&{%l}) -> '%1')? .)* ~}") +assert(re.match("aloalo", + "{~ (((&'al' {.}) -> 'A%1' / (&%l {.}) -> '%1%1') / .)* ~}") == "AallooAalloo") +-- bug in 0.9 (and older versions), due to captures in look-aheads +x = re.compile[[ {~ (&(. ([a-z]* -> '*')) ([a-z]+ -> '+') ' '*)* ~} ]] +assert(x:match"alo alo" == "+ +") + +-- valid capture in look-ahead (used inside the look-ahead itself) +x = re.compile[[ + S <- &({:two: .. :} . =two) {[a-z]+} / . S +]] +assert(x:match("hello aloaLo aloalo xuxu") == "aloalo") + + p = re.compile[[ - block <- ({:ident:' '*:} - ((=ident !' ' ) / &(=ident ' ') )*) -> {} + block <- {| {:ident:space*:} line + ((=ident !space line) / &(=ident space) block)* |} line <- {[^%nl]*} %nl + space <- '_' -- should be ' ', but '_' is simpler for editors ]] t= p:match[[ 1 - 1.1 - 1.2 - 1.2.1 - +__1.1 +__1.2 +____1.2.1 +____ 2 - 2.1 +__2.1 ]] -checkeq(t, {"1", {"1.1", "1.2", {"1.2.1", "", ident = " "}, ident = " "}, - "2", {"2.1", ident = " "}, ident = ""}) +checkeq(t, {"1", {"1.1", "1.2", {"1.2.1", "", ident = "____"}, ident = "__"}, + "2", {"2.1", ident = "__"}, ident = ""}) -- nested grammars p = re.compile[[ - s <- !. - b <- ( x <- ('b' )? ) - a <- ( x <- 'a' ? ) + s <- a b !. + b <- ( x <- ('b' x)? ) + a <- ( x <- 'a' x? ) ]] assert(p:match'aaabbb') @@ -1042,18 +1576,18 @@ assert(not p:match'bbb') assert(not p:match'aaabbba') -- testing groups -t = {re.match("abc", "{:S <- {:.:} {} / '':}")} +t = {re.match("abc", "{:S <- {:.:} {S} / '':}")} checkeq(t, {"a", "bc", "b", "c", "c", ""}) -t = re.match("1234", "({:a:.:} {:b:.:} {:c:.{.}:}) -> {}") +t = re.match("1234", "{| {:a:.:} {:b:.:} {:c:.{.}:} |}") checkeq(t, {a="1", b="2", c="4"}) -t = re.match("1234", "({:a:.:} {:b:{.}{.}:} {:c:{.}:}) -> {}") +t = re.match("1234", "{|{:a:.:} {:b:{.}{.}:} {:c:{.}:}|}") checkeq(t, {a="1", b="2", c="4"}) -t = re.match("12345", "({:.:} {:b:{.}{.}:} {:{.}{.}:}) -> {}") +t = re.match("12345", "{| {:.:} {:b:{.}{.}:} {:{.}{.}:} |}") checkeq(t, {"1", b="2", "4", "5"}) -t = re.match("12345", "({:.:} {:{:b:{.}{.}:}:} {:{.}{.}:}) -> {}") +t = re.match("12345", "{| {:.:} {:{:b:{.}{.}:}:} {:{.}{.}:} |}") checkeq(t, {"1", "23", "4", "5"}) -t = re.match("12345", "({:.:} {{:b:{.}{.}:}} {:{.}{.}:}) -> {}") +t = re.match("12345", "{| {:.:} {{:b:{.}{.}:}} {:{.}{.}:} |}") checkeq(t, {"1", "23", "4", "5"}) @@ -1063,7 +1597,7 @@ assert(os.setlocale("C") == "C") function eqlpeggsub (p1, p2) local s1 = cs2str(re.compile(p1)) local s2 = string.gsub(allchar, "[^" .. p2 .. "]", "") -if s1 ~= s2 then print(s1,s2) end + -- if s1 ~= s2 then print(#s1,#s2) end assert(s1 == s2) end @@ -1076,6 +1610,7 @@ eqlpeggsub("%p", "%p") eqlpeggsub("%d", "%d") eqlpeggsub("%x", "%x") eqlpeggsub("%s", "%s") +eqlpeggsub("%c", "%c") eqlpeggsub("%W", "%W") eqlpeggsub("%A", "%A") @@ -1085,9 +1620,10 @@ eqlpeggsub("%P", "%P") eqlpeggsub("%D", "%D") eqlpeggsub("%X", "%X") eqlpeggsub("%S", "%S") +eqlpeggsub("%C", "%C") eqlpeggsub("[%w]", "%w") -eqlpeggsub("[%w_]", "_%w") +eqlpeggsub("[_%w]", "_%w") eqlpeggsub("[^%w]", "%W") eqlpeggsub("[%W%S]", "%W%S") @@ -1097,22 +1633,35 @@ re.updatelocale() -- testing nested substitutions x string captures p = re.compile[[ - text <- {~ * ~} - item <- / [^()] / '(' * ')' - arg <- ' '* {~ (!',' )* ~} - args <- '(' (',' )* ')' - macro <- ('apply' ) -> '%1(%2)' - / ('add' ) -> '%1 + %2' - / ('mul' ) -> '%1 * %2' + text <- {~ item* ~} + item <- macro / [^()] / '(' item* ')' + arg <- ' '* {~ (!',' item)* ~} + args <- '(' arg (',' arg)* ')' + macro <- ('apply' args) -> '%1(%2)' + / ('add' args) -> '%1 + %2' + / ('mul' args) -> '%1 * %2' ]] assert(p:match"add(mul(a,b), apply(f,x))" == "a * b + f(x)") -rev = re.compile[[ R <- (!.) -> '' / ({.} ) -> '%2%1']] +rev = re.compile[[ R <- (!.) -> '' / ({.} R) -> '%2%1']] assert(rev:match"0123456789" == "9876543210") +-- testing error messages in re + +local function errmsg (p, err) + checkerr(err, re.compile, p) +end + +errmsg('aaaa', "rule 'aaaa'") +errmsg('a', 'outside') +errmsg('b <- a', 'undefined') +errmsg("x <- 'a' x <- 'b'", 'already defined') +errmsg("'a' -", "near '-'") + + print"OK" diff --git a/files/examples/luaiconv/test_iconv.lua b/files/examples/luaiconv/test_iconv.lua new file mode 100644 index 0000000..7b5cc5e --- /dev/null +++ b/files/examples/luaiconv/test_iconv.lua @@ -0,0 +1,127 @@ +-- -*- coding: utf-8 -*- + +local iconv = require("iconv") + +-- Set your terminal encoding here +-- local termcs = "iso-8859-1" +local termcs = "utf-8" + +local iso88591 = "\65\111\32\108\111\110\103\101\44\32\97\111\32\108\117" +.. "\97\114\10\78\111\32\114\105\111\32\117\109\97\32\118\101\108\97\10\83" +.. "\101\114\101\110\97\32\97\32\112\97\115\115\97\114\44\10\81\117\101\32" +.. "\233\32\113\117\101\32\109\101\32\114\101\118\101\108\97\10\10\78\227" +.. "\111\32\115\101\105\44\32\109\97\115\32\109\117\101\32\115\101\114\10" +.. "\84\111\114\110\111\117\45\115\101\45\109\101\32\101\115\116\114\97\110" +.. "\104\111\44\10\69\32\101\117\32\115\111\110\104\111\32\115\101\109\32" +.. "\118\101\114\10\79\115\32\115\111\110\104\111\115\32\113\117\101\32\116" +.. "\101\110\104\111\46\10\10\81\117\101\32\97\110\103\250\115\116\105\97" +.. "\32\109\101\32\101\110\108\97\231\97\63\10\81\117\101\32\97\109\111\114" +.. "\32\110\227\111\32\115\101\32\101\120\112\108\105\99\97\63\10\201\32\97" +.. "\32\118\101\108\97\32\113\117\101\32\112\97\115\115\97\10\78\97\32\110" +.. "\111\105\116\101\32\113\117\101\32\102\105\99\97\10\10\32\32\32\32\45" +.. "\45\32\70\101\114\110\97\110\100\111\32\80\101\115\115\111\97\10" + +local utf8 = "\65\111\32\108\111\110\103\101\44\32\97\111\32\108\117\97\114" +.. "\10\78\111\32\114\105\111\32\117\109\97\32\118\101\108\97\10\83\101\114" +.. "\101\110\97\32\97\32\112\97\115\115\97\114\44\10\81\117\101\32\195\169\32" +.. "\113\117\101\32\109\101\32\114\101\118\101\108\97\10\10\78\195\163\111\32" +.. "\115\101\105\44\32\109\97\115\32\109\117\101\32\115\101\114\10\84\111\114" +.. "\110\111\117\45\115\101\45\109\101\32\101\115\116\114\97\110\104\111\44" +.. "\10\69\32\101\117\32\115\111\110\104\111\32\115\101\109\32\118\101\114\10" +.. "\79\115\32\115\111\110\104\111\115\32\113\117\101\32\116\101\110\104\111" +.. "\46\10\10\81\117\101\32\97\110\103\195\186\115\116\105\97\32\109\101\32" +.. "\101\110\108\97\195\167\97\63\10\81\117\101\32\97\109\111\114\32\110\195" +.. "\163\111\32\115\101\32\101\120\112\108\105\99\97\63\10\195\137\32\97\32" +.. "\118\101\108\97\32\113\117\101\32\112\97\115\115\97\10\78\97\32\110\111" +.. "\105\116\101\32\113\117\101\32\102\105\99\97\10\10\32\32\32\32\45\45\32" +.. "\70\101\114\110\97\110\100\111\32\80\101\115\115\111\97\10" + +local utf16 = "\255\254\65\0\111\0\32\0\108\0\111\0\110\0\103\0\101\0\44\0\32" +.. "\0\97\0\111\0\32\0\108\0\117\0\97\0\114\0\10\0\78\0\111\0\32\0\114\0\105" +.. "\0\111\0\32\0\117\0\109\0\97\0\32\0\118\0\101\0\108\0\97\0\10\0\83\0\101" +.. "\0\114\0\101\0\110\0\97\0\32\0\97\0\32\0\112\0\97\0\115\0\115\0\97\0\114" +.. "\0\44\0\10\0\81\0\117\0\101\0\32\0\233\0\32\0\113\0\117\0\101\0\32\0\109" +.. "\0\101\0\32\0\114\0\101\0\118\0\101\0\108\0\97\0\10\0\10\0\78\0\227\0\111" +.. "\0\32\0\115\0\101\0\105\0\44\0\32\0\109\0\97\0\115\0\32\0\109\0\117\0\101" +.. "\0\32\0\115\0\101\0\114\0\10\0\84\0\111\0\114\0\110\0\111\0\117\0\45\0\115" +.. "\0\101\0\45\0\109\0\101\0\32\0\101\0\115\0\116\0\114\0\97\0\110\0\104\0" +.. "\111\0\44\0\10\0\69\0\32\0\101\0\117\0\32\0\115\0\111\0\110\0\104\0\111" +.. "\0\32\0\115\0\101\0\109\0\32\0\118\0\101\0\114\0\10\0\79\0\115\0\32\0\115" +.. "\0\111\0\110\0\104\0\111\0\115\0\32\0\113\0\117\0\101\0\32\0\116\0\101\0" +.. "\110\0\104\0\111\0\46\0\10\0\10\0\81\0\117\0\101\0\32\0\97\0\110\0\103\0" +.. "\250\0\115\0\116\0\105\0\97\0\32\0\109\0\101\0\32\0\101\0\110\0\108\0\97" +.. "\0\231\0\97\0\63\0\10\0\81\0\117\0\101\0\32\0\97\0\109\0\111\0\114\0\32" +.. "\0\110\0\227\0\111\0\32\0\115\0\101\0\32\0\101\0\120\0\112\0\108\0\105\0" +.. "\99\0\97\0\63\0\10\0\201\0\32\0\97\0\32\0\118\0\101\0\108\0\97\0\32\0\113" +.. "\0\117\0\101\0\32\0\112\0\97\0\115\0\115\0\97\0\10\0\78\0\97\0\32\0\110" +.. "\0\111\0\105\0\116\0\101\0\32\0\113\0\117\0\101\0\32\0\102\0\105\0\99\0" +.. "\97\0\10\0\10\0\32\0\32\0\32\0\32\0\45\0\45\0\32\0\70\0\101\0\114\0\110" +.. "\0\97\0\110\0\100\0\111\0\32\0\80\0\101\0\115\0\115\0\111\0\97\0\10\0" + +-- Bizarre EBCDIC-CP-ES encoding. +local ebcdic = "\193\150\64\147\150\149\135\133\107\64\129\150\64\147\164\129" +.. "\153\37\213\150\64\153\137\150\64\164\148\129\64\165\133\147\129\37\226" +.. "\133\153\133\149\129\64\129\64\151\129\162\162\129\153\107\37\216\164\133" +.. "\64\81\64\152\164\133\64\148\133\64\153\133\165\133\147\129\37\37\213\70" +.. "\150\64\162\133\137\107\64\148\129\162\64\148\164\133\64\162\133\153\37" +.. "\227\150\153\149\150\164\96\162\133\96\148\133\64\133\162\163\153\129\149" +.. "\136\150\107\37\197\64\133\164\64\162\150\149\136\150\64\162\133\148\64" +.. "\165\133\153\37\214\162\64\162\150\149\136\150\162\64\152\164\133\64\163" +.. "\133\149\136\150\75\37\37\216\164\133\64\129\149\135\222\162\163\137\129" +.. "\64\148\133\64\133\149\147\129\72\129\111\37\216\164\133\64\129\148\150" +.. "\153\64\149\70\150\64\162\133\64\133\167\151\147\137\131\129\111\37\113" +.. "\64\129\64\165\133\147\129\64\152\164\133\64\151\129\162\162\129\37\213" +.. "\129\64\149\150\137\163\133\64\152\164\133\64\134\137\131\129\37\37\64\64" +.. "\64\64\96\96\64\198\133\153\149\129\149\132\150\64\215\133\162\162\150\129" +.. "\37" + + +local function ErrMsg(errno) + if errno == iconv.ERROR_INCOMPLETE then + return "Incomplete input." + elseif errno == iconv.ERROR_INVALID then + return "Invalid input." + elseif errno == iconv.ERROR_NO_MEMORY then + return "Failed to allocate memory." + elseif errno == iconv.ERROR_UNKNOWN then + return "There was an unknown error." + elseif errno == iconv.FINALIZED then + return "Handle was already finalized." + end + return "Unknown error: "..tostring(errno) +end + + +local function check_one(to, from, text) + print("\n-- Testing conversion from " .. from .. " to " .. to) + local cd, errno = iconv.new(to .. "//TRANSLIT", from) + assert(cd, "Failed to create a converter object: " .. ErrMsg(errno)) + local ostr, err = cd:iconv(text) + + if err then + print("ERROR: " .. ErrMsg(err)) + end + print(ostr) +end + +check_one(termcs, "iso-8859-1", iso88591) +check_one(termcs, "utf-8", utf8) +check_one(termcs, "utf-16", utf16) + + +-- The library must never crash the interpreter, even if the user tweaks +-- with the garbage collector methods. +local _, e, cd, s, gc +cd = iconv.new("iso-8859-1", "utf-8") +_, e = cd:iconv("atenção") +assert(e == nil, "Unexpected conversion error") +gc = getmetatable(cd).__gc +gc(cd) +_, e = cd:iconv("atenção") +assert(e == iconv.ERROR_FINALIZED, "Failed to detect double-freed objects") +gc(cd) + +cd = iconv.new("iso-8859-1", "utf-8") +s, e = cd:iconv("atenção") +assert(s == "aten\231\227o", "Unexpected result for valid conversion") +assert(e == nil, "Unexpected return value for valid conversion") diff --git a/files/examples/luajson/hook_require.lua b/files/examples/luajson/hook_require.lua index 4853c38..9586a4b 100644 --- a/files/examples/luajson/hook_require.lua +++ b/files/examples/luajson/hook_require.lua @@ -1,6 +1,7 @@ local os = require("os") local old_require = require if os.getenv('LUA_OLD_INIT') then + local loadstring = loadstring or load assert(loadstring(os.getenv('LUA_OLD_INIT')))() else require("luarocks.require") diff --git a/files/examples/luajson/lunit-calls.lua b/files/examples/luajson/lunit-calls.lua index 2c7a65b..b8450f4 100644 --- a/files/examples/luajson/lunit-calls.lua +++ b/files/examples/luajson/lunit-calls.lua @@ -1,3 +1,4 @@ +local lpeg = require("lpeg") local json = require("json") local lunit = require("lunit") local math = require("math") @@ -7,7 +8,11 @@ local encode = json.encode -- DECODE NOT 'local' due to requirement for testutil to access it decode = json.decode.getDecoder(false) -module("lunit-calls", lunit.testcase, package.seeall) +if not module then + _ENV = lunit.module("lunit-calls", 'seeall') +else + module("lunit-calls", lunit.testcase, package.seeall) +end function setup() -- Ensure that the decoder is reset @@ -101,11 +106,48 @@ end function test_permitted() local strict = { calls = { - defs = { call = true } + defs = { call = true, other = true } } } local decoder = json.decode.getDecoder(strict) assert(decoder("call(1)").name == 'call') + assert(decoder("other(1)").name == 'other') +end + +function test_permitted_trailing() + local strict = { + calls = { + defs = { call = true, other = true } + } + } + local decoder = json.decode.getDecoder(strict) + assert(decoder("call(1,)").name == 'call') + assert(decoder("other(1,)").name == 'other') +end +function test_permitted_no_trailing() + local strict = { + calls = { + defs = { call = true, other = true }, + trailingComma = false + } + } + local decoder = json.decode.getDecoder(strict) + assert_error(function() + decoder("call(1,)") + end) + assert_error(function() + decoder("other(1,)") + end) +end +function test_permitted_nested() + local strict = { + calls = { + defs = { call = true, other = true } + } + } + local decoder = json.decode.getDecoder(strict) + assert(decoder("call(call(1))").name == 'call') + assert(decoder("other(call(1))").name == 'other') end function test_not_defined_fail() diff --git a/files/examples/luajson/lunit-depth.lua b/files/examples/luajson/lunit-depth.lua index c49186e..691e43b 100644 --- a/files/examples/luajson/lunit-depth.lua +++ b/files/examples/luajson/lunit-depth.lua @@ -1,7 +1,11 @@ local lunit = require("lunit") local json = require("json") -module("lunit-depth", lunit.testcase, package.seeall) +if not module then + _ENV = lunit.module("lunit-depth", 'seeall') +else + module("lunit-depth", lunit.testcase, package.seeall) +end local SAFE_DEPTH = 23 local SAFE_CALL_DEPTH = 31 diff --git a/files/examples/luajson/lunit-empties-decode.lua b/files/examples/luajson/lunit-empties-decode.lua new file mode 100644 index 0000000..61207cc --- /dev/null +++ b/files/examples/luajson/lunit-empties-decode.lua @@ -0,0 +1,273 @@ +local json = require("json") +local lunit = require("lunit") + +-- Test module for handling the decoding with 'empties' allowed +if not module then + _ENV = lunit.module("lunit-empties-decode", 'seeall') +else + module("lunit-empties-decode", lunit.testcase, package.seeall) +end + +local options = { + array = { + allowEmptyElement = true + }, + calls = { + allowEmptyElement = true, + allowUndefined = true + }, + object = { + allowEmptyElement = true, + } +} +local options_notrailing = { + array = { + allowEmptyElement = true, + trailingComma = false + }, + calls = { + allowEmptyElement = true, + allowUndefined = true, + trailingComma = false + }, + object = { + allowEmptyElement = true, + trailingComma = false + } +} +local options_simple_null = { + array = { + allowEmptyElement = true + }, + calls = { + allowEmptyElement = true, + allowUndefined = true + }, + object = { + allowEmptyElement = true, + }, + others = { + null = false, + undefined = false + } +} + +function test_decode_array_with_only_null() + local result = assert(json.decode('[null]', options_simple_null)) + assert_nil(result[1]) + assert_equal(1, result.n) + local result = assert(json.decode('[null]', options)) + assert_equal(json.util.null, result[1]) + assert_equal(1, #result) +end + +function test_decode_array_with_empties() + local result = assert(json.decode('[,]', options_simple_null)) + assert_nil(result[1]) + assert_equal(1, result.n) + local result = assert(json.decode('[,]', options)) + assert_equal(json.util.undefined, result[1]) + assert_equal(1, #result) + + local result = assert(json.decode('[,]', options_notrailing)) + assert_equal(json.util.undefined, result[1]) + assert_equal(json.util.undefined, result[2]) + assert_equal(2, #result) +end + +function test_decode_array_with_null() + local result = assert(json.decode('[1, null, 3]', options_simple_null)) + assert_equal(1, result[1]) + assert_nil(result[2]) + assert_equal(3, result[3]) + assert_equal(3, result.n) + local result = assert(json.decode('[1, null, 3]', options)) + assert_equal(1, result[1]) + assert_equal(json.util.null, result[2]) + assert_equal(3, result[3]) +end +function test_decode_array_with_empty() + local result = assert(json.decode('[1,, 3]', options_simple_null)) + assert_equal(1, result[1]) + assert_nil(result[2]) + assert_equal(3, result[3]) + assert_equal(3, result.n) + local result = assert(json.decode('[1,, 3]', options)) + assert_equal(1, result[1]) + assert_equal(json.util.undefined, result[2]) + assert_equal(3, result[3]) +end + +function test_decode_small_array_with_trailing_null() + local result = assert(json.decode('[1, null]', options_simple_null)) + assert_equal(1, result[1]) + assert_nil(result[2]) + assert_equal(2, result.n) + local result = assert(json.decode('[1, ]', options_simple_null)) + assert_equal(1, result[1]) + assert_equal(1, #result) + local result = assert(json.decode('[1, ]', options)) + assert_equal(1, result[1]) + assert_equal(1, #result) + local result = assert(json.decode('[1, ]', options_notrailing)) + assert_equal(1, result[1]) + assert_equal(json.util.undefined, result[2]) + assert_equal(2, #result) +end + +function test_decode_array_with_trailing_null() + local result = assert(json.decode('[1, null, 3, null]', options_simple_null)) + assert_equal(1, result[1]) + assert_nil(result[2]) + assert_equal(3, result[3]) + assert_nil(result[4]) + assert_equal(4, result.n) + local result = assert(json.decode('[1, null, 3, null]', options)) + assert_equal(1, result[1]) + assert_equal(json.util.null, result[2]) + assert_equal(3, result[3]) + assert_equal(json.util.null, result[4]) + assert_equal(4, #result) + local result = assert(json.decode('[1, , 3, ]', options)) + assert_equal(1, result[1]) + assert_equal(json.util.undefined, result[2]) + assert_equal(3, result[3]) + assert_equal(3, #result) + local result = assert(json.decode('[1, , 3, ]', options_notrailing)) + assert_equal(1, result[1]) + assert_equal(json.util.undefined, result[2]) + assert_equal(3, result[3]) + assert_equal(json.util.undefined, result[4]) + assert_equal(4, #result) +end + +function test_decode_object_with_null() + local result = assert(json.decode('{x: null}', options_simple_null)) + assert_nil(result.x) + assert_nil(next(result)) + + local result = assert(json.decode('{x: null}', options)) + assert_equal(json.util.null, result.x) + + local result = assert(json.decode('{x: }', options_simple_null)) + assert_nil(result.x) + assert_nil(next(result)) + + local result = assert(json.decode('{x: }', options)) + assert_equal(json.util.undefined, result.x) + + -- Handle the trailing comma case + local result = assert(json.decode('{x: ,}', options_simple_null)) + assert_nil(result.x) + assert_nil(next(result)) + + local result = assert(json.decode('{x: ,}', options)) + assert_equal(json.util.undefined, result.x) + + -- NOTE: Trailing comma must be allowed explicitly in this case + assert_error(function() + json.decode('{x: ,}', options_notrailing) + end) + + -- Standard setup doesn't allow empties + assert_error(function() + json.decode('{x: }') + end) +end +function test_decode_bigger_object_with_null() + local result = assert(json.decode('{y: 1, x: null}', options_simple_null)) + assert_equal(1, result.y) + assert_nil(result.x) + + local result = assert(json.decode('{y: 1, x: null}', options)) + assert_equal(1, result.y) + assert_equal(json.util.null, result.x) + + local result = assert(json.decode('{y: 1, x: }', options_simple_null)) + assert_equal(1, result.y) + assert_nil(result.x) + local result = assert(json.decode('{x: , y: 1}', options_simple_null)) + assert_equal(1, result.y) + assert_nil(result.x) + + local result = assert(json.decode('{y: 1, x: }', options)) + assert_equal(1, result.y) + assert_equal(json.util.undefined, result.x) + + local result = assert(json.decode('{x: , y: 1}', options)) + assert_equal(1, result.y) + assert_equal(json.util.undefined, result.x) + + -- Handle the trailing comma case + local result = assert(json.decode('{y: 1, x: , }', options_simple_null)) + assert_equal(1, result.y) + assert_nil(result.x) + local result = assert(json.decode('{x: , y: 1, }', options_simple_null)) + assert_equal(1, result.y) + assert_nil(result.x) + + local result = assert(json.decode('{y: 1, x: ,}', options)) + assert_equal(1, result.y) + assert_equal(json.util.undefined, result.x) + + local result = assert(json.decode('{x: , y: 1, }', options)) + assert_equal(1, result.y) + assert_equal(json.util.undefined, result.x) + + -- NOTE: Trailing comma must be allowed explicitly in this case as there is no such thing as an "empty" key:value pair + assert_error(function() + json.decode('{y: 1, x: ,}', options_notrailing) + end) + assert_error(function() + json.decode('{x: , y: 1, }', options_notrailing) + end) +end + +function test_decode_call_with_empties() + local result = assert(json.decode('call(,)', options_simple_null)) + result = result.parameters + assert_nil(result[1]) + assert_equal(1, result.n) + local result = assert(json.decode('call(,)', options)) + result = result.parameters + assert_equal(json.util.undefined, result[1]) + assert_equal(1, #result) + + local result = assert(json.decode('call(,)', options_notrailing)) + result = result.parameters + assert_equal(json.util.undefined, result[1]) + assert_equal(json.util.undefined, result[2]) + assert_equal(2, #result) +end + + + +function test_call_with_empties_and_trailing() + local result = assert(json.decode('call(1, null, 3, null)', options_simple_null)) + result = result.parameters + assert_equal(1, result[1]) + assert_nil(result[2]) + assert_equal(3, result[3]) + assert_nil(result[4]) + assert_equal(4, result.n) + local result = assert(json.decode('call(1, null, 3, null)', options)) + result = result.parameters + assert_equal(1, result[1]) + assert_equal(json.util.null, result[2]) + assert_equal(3, result[3]) + assert_equal(json.util.null, result[4]) + assert_equal(4, #result) + local result = assert(json.decode('call(1, , 3, )', options)) + result = result.parameters + assert_equal(1, result[1]) + assert_equal(json.util.undefined, result[2]) + assert_equal(3, result[3]) + assert_equal(3, #result) + local result = assert(json.decode('call(1, , 3, )', options_notrailing)) + result = result.parameters + assert_equal(1, result[1]) + assert_equal(json.util.undefined, result[2]) + assert_equal(3, result[3]) + assert_equal(json.util.undefined, result[4]) + assert_equal(4, #result) +end diff --git a/files/examples/luajson/lunit-encoderfunc.lua b/files/examples/luajson/lunit-encoderfunc.lua index c07c764..f265d76 100644 --- a/files/examples/luajson/lunit-encoderfunc.lua +++ b/files/examples/luajson/lunit-encoderfunc.lua @@ -2,10 +2,15 @@ local json = require("json") local lunit = require("lunit") local math = require("math") local testutil = require("testutil") +local unpack = require("table").unpack or unpack local setmetatable = setmetatable -module("lunit-encoderfunc", lunit.testcase, package.seeall) +if not module then + _ENV = lunit.module("lunit-encoderfunc", 'seeall') +else + module("lunit-encoderfunc", lunit.testcase, package.seeall) +end local function build_call(name, parameters) return json.util.buildCall(name, unpack(parameters, parameters.n)) diff --git a/files/examples/luajson/lunit-encoding.lua b/files/examples/luajson/lunit-encoding.lua index ff4ba28..f8dff89 100644 --- a/files/examples/luajson/lunit-encoding.lua +++ b/files/examples/luajson/lunit-encoding.lua @@ -1,7 +1,11 @@ local json = require("json") local lunit = require("lunit") -module("lunit-encoding", lunit.testcase, package.seeall) +if not module then + _ENV = lunit.module("lunit-encoding", 'seeall') +else + module("lunit-encoding", lunit.testcase, package.seeall) +end function test_cloned_array_sibling() local obj = {} @@ -70,3 +74,18 @@ function test_custom_encode() encoder(obj) assert_true(sawX) end + +function test_custom_array() + assert_equal("[]", json.encode(setmetatable({}, {__is_luajson_array = true}))) + assert_equal("[]", json.encode(json.util.InitArray({}))) +end + +function test_undefined() + assert_equal("[undefined]", json.encode({ json.util.undefined })) +end + +function test_unknown() + assert_error("Expected attempting to encode an unregistered function to fail", function() + json.encode({ function() end }) + end) +end diff --git a/files/examples/luajson/lunit-nothrow-decode.lua b/files/examples/luajson/lunit-nothrow-decode.lua new file mode 100644 index 0000000..626485d --- /dev/null +++ b/files/examples/luajson/lunit-nothrow-decode.lua @@ -0,0 +1,26 @@ +local json = require("json") +local lunit = require("lunit") + +-- Test module for handling the simple decoding that behaves more like expected +if not module then + _ENV = lunit.module("lunit-nothrow-decode", 'seeall') +else + module("lunit-nothrow-decode", lunit.testcase, package.seeall) +end + +function test_decode_nothrow_bad_data() + assert_nil((json.decode('x', {nothrow = true}))) + assert_nil((json.decode('{x:x}', {nothrow = true}))) + assert_nil((json.decode('[x:x]', {nothrow = true}))) + assert_nil((json.decode('[1.fg]', {nothrow = true}))) + assert_nil((json.decode('["\\xzz"]', {nothrow = true}))) +end + +function test_decode_nothrow_ok_data() + assert_not_nil((json.decode('"x"', {nothrow = true}))) + assert_not_nil((json.decode('{x:"x"}', {nothrow = true}))) + assert_not_nil((json.decode('["x"]', {nothrow = true}))) + assert_not_nil((json.decode('[1.0]', {nothrow = true}))) + assert_not_nil((json.decode('["\\u00FF"]', {nothrow = true}))) +end + diff --git a/files/examples/luajson/lunit-numbers.lua b/files/examples/luajson/lunit-numbers.lua index 2d42fd7..a9f1e41 100644 --- a/files/examples/luajson/lunit-numbers.lua +++ b/files/examples/luajson/lunit-numbers.lua @@ -8,22 +8,35 @@ local encode = json.encode -- DECODE NOT 'local' due to requirement for testutil to access it decode = json.decode.getDecoder(false) -module("lunit-numbers", lunit.testcase, package.seeall) +local TEST_ENV +if not module then + _ENV = lunit.module("lunit-numbers", 'seeall') + TEST_ENV = _ENV +else + module("lunit-numbers", lunit.testcase, package.seeall) + TEST_ENV = _M +end function setup() -- Ensure that the decoder is reset _G["decode"] = json.decode.getDecoder(false) end -local function assert_near(expect, received) +local function is_near(expect, received) local pctDiff if expect == received then pctDiff = 0 else pctDiff = math.abs(1 - expect / received) end - local msg = ("expected '%s' but was '%s' .. '%s'%% apart"):format(expect, received, pctDiff * 100) - assert(pctDiff < 0.000001, msg) + if pctDiff < 0.000001 then + return true + else + return false, ("expected '%s' but was '%s' .. '%s'%% apart"):format(expect, received, pctDiff * 100) + end +end +local function assert_near(expect, received) + assert(is_near(expect, received)) end local function test_simple(num) assert_near(num, decode(tostring(num))) @@ -35,14 +48,24 @@ local function test_scientific(num) assert_near(num, decode(string.format('%e', num))) assert_near(num, decode(string.format('%E', num))) end +local function test_scientific_denied(num) + local decode = json.decode.getDecoder({ number = { exp = false } }) + assert_error_match("Exponent-denied error did not match", "Exponents.*denied", function() + decode(string.format('%e', num)) + end) + assert_error_match("Exponent-denied error did not match", "Exponents.*denied", function() + decode(string.format('%E', num)) + end) +end local numbers = { 0, 1, -1, math.pi, -math.pi } math.randomseed(0xDEADBEEF) +local pow = math.pow or load("return function(a, b) return a ^ b end")() -- Add sequence of numbers at low/high end of value-set for i = -300,300,60 do - numbers[#numbers + 1] = math.random() * math.pow(10, i) - numbers[#numbers + 1] = -math.random() * math.pow(10, i) + numbers[#numbers + 1] = math.random() * pow(10, i) + numbers[#numbers + 1] = -math.random() * pow(10, i) end local function get_number_tester(f) @@ -53,9 +76,35 @@ local function get_number_tester(f) end end +local function test_fraction(num) + assert_near(num, decode(string.format("%f", num))) +end +local function test_fraction_denied(num) + local decode = json.decode.getDecoder({ number = { frac = false } }) + local formatted = string.format('%f', num) + assert_error_match("Fraction-denied error did not match for " .. formatted, "Fractions.*denied", function() + decode(formatted) + end) +end +local function get_number_fraction_tester(f) + return function () + for _, v in ipairs(numbers) do + -- Fractional portion must be present + local formatted = string.format("%f", v) + -- San check that the formatted value is near the desired value + if nil ~= formatted:find("%.") and is_near(v, tonumber(formatted)) then + f(v) + end + end + end +end + test_simple_numbers = get_number_tester(test_simple) test_simple_numbers_w_encode = get_number_tester(test_simple_w_encode) test_simple_numbers_scientific = get_number_tester(test_scientific) +test_simple_numbers_scientific_denied = get_number_tester(test_scientific_denied) +test_simple_numbers_fraction_only = get_number_fraction_tester(test_fraction) +test_simple_numbers_fraction_denied_only = get_number_fraction_tester(test_fraction_denied) function test_infinite_nostrict() assert_equal(math.huge, decode("Infinity")) @@ -69,6 +118,7 @@ function test_nan_nostrict() assert_true(value ~= value) local value = decode("NaN") assert_true(value ~= value) + assert_equal("NaN", encode(decode("NaN"))) end function test_expression() @@ -139,13 +189,13 @@ local function buildFailedStrictDecoder(f) return testutil.buildFailedPatchedDecoder(f, strictDecoder) end -- SETUP CHECKS FOR SEQUENCE OF DECODERS -for k, v in pairs(_M) do +for k, v in pairs(TEST_ENV) do if k:match("^test_") and not k:match("_gen$") and not k:match("_only$") then if k:match("_nostrict") then - _M[k .. "_strict_gen"] = buildFailedStrictDecoder(v) + TEST_ENV[k .. "_strict_gen"] = buildFailedStrictDecoder(v) else - _M[k .. "_strict_gen"] = buildStrictDecoder(v) + TEST_ENV[k .. "_strict_gen"] = buildStrictDecoder(v) end - _M[k .. "_hex_gen"] = testutil.buildPatchedDecoder(v, hexDecoder) + TEST_ENV[k .. "_hex_gen"] = testutil.buildPatchedDecoder(v, hexDecoder) end end diff --git a/files/examples/luajson/lunit-simple-decode.lua b/files/examples/luajson/lunit-simple-decode.lua index 73c4c45..86acace 100644 --- a/files/examples/luajson/lunit-simple-decode.lua +++ b/files/examples/luajson/lunit-simple-decode.lua @@ -2,7 +2,11 @@ local json = require("json") local lunit = require("lunit") -- Test module for handling the simple decoding that behaves more like expected -module("lunit-simple-decode", lunit.testcase, package.seeall) +if not module then + _ENV = lunit.module("lunit-simple-decode", 'seeall') +else + module("lunit-simple-decode", lunit.testcase, package.seeall) +end function test_decode_simple_undefined() assert_nil(json.decode('undefined', json.decode.simple)) @@ -19,6 +23,18 @@ function test_decode_default_null() assert_equal(json.util.null, json.decode('null')) end +function test_decode_array_simple_with_only_null() + local result = assert(json.decode('[null]', json.decode.simple)) + assert_nil(result[1]) + assert_equal(1, result.n) +end + +function test_decode_array_default_with_only_null() + local result = assert(json.decode('[null]')) + assert_equal(json.util.null, result[1]) + assert_equal(1, #result) +end + function test_decode_array_simple_with_null() local result = assert(json.decode('[1, null, 3]', json.decode.simple)) assert_equal(1, result[1]) @@ -35,6 +51,55 @@ function test_decode_array_default_with_null() assert_equal(3, #result) end +function test_decode_small_array_simple_with_trailing_null() + local result = assert(json.decode('[1, null]', json.decode.simple)) + assert_equal(1, result[1]) + assert_nil(result[2]) + assert_equal(2, result.n) +end + +function test_decode_small_array_default_with_trailing_null() + local result = assert(json.decode('[1, null]')) + assert_equal(1, result[1]) + assert_equal(json.util.null, result[2]) + assert_equal(2, #result) +end + + +function test_decode_small_array_simple_with_trailing_null() + local result = assert(json.decode('[1, null]', json.decode.simple)) + assert_equal(1, result[1]) + assert_nil(result[2]) + assert_equal(2, result.n) +end + +function test_decode_small_array_default_with_trailing_null() + local result = assert(json.decode('[1, null]')) + assert_equal(1, result[1]) + assert_equal(json.util.null, result[2]) + assert_equal(2, #result) +end + + +function test_decode_array_simple_with_trailing_null() + local result = assert(json.decode('[1, null, 3, null]', json.decode.simple)) + assert_equal(1, result[1]) + assert_nil(result[2]) + assert_equal(3, result[3]) + assert_nil(result[4]) + assert_equal(4, result.n) +end + +function test_decode_array_default_with_trailing_null() + local result = assert(json.decode('[1, null, 3, null]')) + assert_equal(1, result[1]) + assert_equal(json.util.null, result[2]) + assert_equal(3, result[3]) + assert_equal(json.util.null, result[4]) + assert_equal(4, #result) +end + + function test_decode_object_simple_with_null() local result = assert(json.decode('{x: null}', json.decode.simple)) assert_nil(result.x) diff --git a/files/examples/luajson/lunit-strings.lua b/files/examples/luajson/lunit-strings.lua index bedf4aa..2e94767 100644 --- a/files/examples/luajson/lunit-strings.lua +++ b/files/examples/luajson/lunit-strings.lua @@ -9,7 +9,11 @@ decode = json.decode.getDecoder(false) local error = error -module("lunit-strings", lunit.testcase, package.seeall) +if not module then + _ENV = lunit.module("lunit-strings", 'seeall') +else + module("lunit-strings", lunit.testcase, package.seeall) +end local function assert_table_equal(expect, t) if type(expect) ~= 'table' then diff --git a/files/examples/luajson/lunit-tests.lua b/files/examples/luajson/lunit-tests.lua index 30e82df..d2f183f 100644 --- a/files/examples/luajson/lunit-tests.lua +++ b/files/examples/luajson/lunit-tests.lua @@ -1,10 +1,18 @@ local json = require("json") local lunit = require("lunit") local testutil = require("testutil") +local lpeg = require("lpeg") -- DECODE NOT 'local' due to requirement for testutil to access it decode = json.decode.getDecoder(false) -module("lunit-tests", lunit.testcase, package.seeall) +local TEST_ENV +if not module then + _ENV = lunit.module("lunit-tests", 'seeall') + TEST_ENV = _ENV +else + module("lunit-tests", lunit.testcase, package.seeall) + TEST_ENV = _M +end function setup() _G["decode"] = json.decode.getDecoder(false) @@ -35,6 +43,13 @@ function test_preprocess() assert_equal('-Infinity', json.encode(1/0, {preProcess = function(x) return -x end})) end +function test_additionalEscapes_only() + -- Test that additionalEscapes is processed on its own - side-stepping normal processing + assert_equal("Hello\\?", json.decode([["\S"]], { strings = { additionalEscapes = lpeg.C(lpeg.P("S")) / "Hello\\?" } })) + -- Test that additionalEscapes overrides any builtin handling + assert_equal("Hello\\?", json.decode([["\n"]], { strings = { additionalEscapes = lpeg.C(lpeg.P("n")) / "Hello\\?" } })) +end + local strictDecoder = json.decode.getDecoder(true) local function buildStrictDecoder(f) @@ -44,12 +59,12 @@ local function buildFailedStrictDecoder(f) return testutil.buildFailedPatchedDecoder(f, strictDecoder) end -- SETUP CHECKS FOR SEQUENCE OF DECODERS -for k, v in pairs(_M) do - if k:match("^test_") and not k:match("_gen$") then +for k, v in pairs(TEST_ENV) do + if k:match("^test_") and not k:match("_gen$") and not k:match("_only$") then if k:match("_nostrict") then - _M[k .. "_strict_gen"] = buildFailedStrictDecoder(v) + TEST_ENV[k .. "_strict_gen"] = buildFailedStrictDecoder(v) else - _M[k .. "_strict_gen"] = buildStrictDecoder(v) + TEST_ENV[k .. "_strict_gen"] = buildStrictDecoder(v) end end end diff --git a/files/examples/luajson/regressionTest.lua b/files/examples/luajson/regressionTest.lua index e5711a6..c100873 100644 --- a/files/examples/luajson/regressionTest.lua +++ b/files/examples/luajson/regressionTest.lua @@ -1,9 +1,9 @@ -- Additional path that may be required -require("json") +local json = require("json") local io = require("io") local os = require("os") -require("lfs") +local lfs = require("lfs") local success = true diff --git a/files/examples/luajson/testutil.lua b/files/examples/luajson/testutil.lua index 66d8595..3667a03 100644 --- a/files/examples/luajson/testutil.lua +++ b/files/examples/luajson/testutil.lua @@ -5,18 +5,23 @@ local assert_error = lunit.assert_error -- Allow module to alter decoder local function setDecoder(d) - decode = d + _G.decode = d end -module("testutil", package.seeall) -function buildPatchedDecoder(f, newDecoder) + +local function buildPatchedDecoder(f, newDecoder) return function() setDecoder(newDecoder) f() end end -function buildFailedPatchedDecoder(f, newDecoder) +local function buildFailedPatchedDecoder(f, newDecoder) return function() setDecoder(newDecoder) assert_error(f) end end + +return { + buildPatchedDecoder = buildPatchedDecoder, + buildFailedPatchedDecoder = buildFailedPatchedDecoder +} diff --git a/files/examples/luajson/utf8_processor.lua b/files/examples/luajson/utf8_processor.lua index f49e15b..88f22b6 100644 --- a/files/examples/luajson/utf8_processor.lua +++ b/files/examples/luajson/utf8_processor.lua @@ -2,7 +2,9 @@ local lpeg = require("lpeg") local string = string -module("utf8_processor") +local floor = require("math").floor + +local _ENV = nil local function encode_utf(codepoint) if codepoint > 0x10FFFF then @@ -10,7 +12,7 @@ local function encode_utf(codepoint) elseif codepoint > 0xFFFF then -- Surrogate pair needed codepoint = codepoint - 0x10000 - local first, second = codepoint / 0x0400 + 0xD800, codepoint % 0x0400 + 0xDC00 + local first, second = floor(codepoint / 0x0400) + 0xD800, codepoint % 0x0400 + 0xDC00 return ("\\u%.4X\\u%.4X"):format(first, second) else return ("\\u%.4X"):format(codepoint) @@ -45,6 +47,10 @@ local utf8 = lpeg.R("\0\127") -- Do nothing here local utf8_decode_pattern = lpeg.Cs(utf8^0) * -1 -function process(s) +local function process(s) return utf8_decode_pattern:match(s) end + +return { + process = process +} diff --git a/files/lua/json.lua b/files/lua/json.lua index 296fb0d..545e74f 100644 --- a/files/lua/json.lua +++ b/files/lua/json.lua @@ -6,7 +6,19 @@ local decode = require("json.decode") local encode = require("json.encode") local util = require("json.util") -module("json") -_M.decode = decode -_M.encode = encode -_M.util = util +local _G = _G + +local _ENV = nil + +local json = { + _VERSION = "1.3.4", + _DESCRIPTION = "LuaJSON : customizable JSON decoder/encoder", + _COPYRIGHT = "Copyright (c) 2007-2017 Thomas Harning Jr. ", + decode = decode, + encode = encode, + util = util +} + +_G.json = json + +return json diff --git a/files/lua/json/decode.lua b/files/lua/json/decode.lua index b856dd5..b2c357c 100644 --- a/files/lua/json/decode.lua +++ b/files/lua/json/decode.lua @@ -5,115 +5,133 @@ local lpeg = require("lpeg") local error = error +local pcall = pcall -local object = require("json.decode.object") -local array = require("json.decode.array") - -local merge = require("json.util").merge +local jsonutil = require("json.util") +local merge = jsonutil.merge local util = require("json.decode.util") +local decode_state = require("json.decode.state") + local setmetatable, getmetatable = setmetatable, getmetatable local assert = assert local ipairs, pairs = ipairs, pairs local string_char = require("string").char +local type = type + local require = require -module("json.decode") + +local _ENV = nil local modulesToLoad = { - "array", - "object", + "composite", "strings", "number", - "calls", "others" } local loadedModules = { } -default = { +local json_decode = {} + +json_decode.default = { unicodeWhitespace = true, - initialObject = false + initialObject = false, + nothrow = false } local modes_defined = { "default", "strict", "simple" } -simple = {} +json_decode.simple = {} -strict = { +json_decode.strict = { unicodeWhitespace = true, - initialObject = true + initialObject = true, + nothrow = false } --- Register generic value type -util.register_type("VALUE") for _,name in ipairs(modulesToLoad) do local mod = require("json.decode." .. name) - for _, mode in pairs(modes_defined) do - if mod[mode] then - _M[mode][name] = mod[mode] + if mod.mergeOptions then + for _, mode in pairs(modes_defined) do + mod.mergeOptions(json_decode[mode], mode) end end - loadedModules[name] = mod - -- Register types - if mod.register_types then - mod.register_types() - end + loadedModules[#loadedModules + 1] = mod end -- Shift over default into defaultOptions to permit build optimization -local defaultOptions = default -default = nil - +local defaultOptions = json_decode.default +json_decode.default = nil + +local function generateDecoder(lexer, options) + -- Marker to permit detection of final end + local marker = {} + local parser = lpeg.Ct((options.ignored * lexer)^0 * lpeg.Cc(marker)) * options.ignored * (lpeg.P(-1) + util.unexpected()) + local decoder = function(data) + local state = decode_state.create(options) + local parsed = parser:match(data) + assert(parsed, "Invalid JSON data") + local i = 0 + while true do + i = i + 1 + local item = parsed[i] + if item == marker then break end + if type(item) == 'function' and item ~= jsonutil.undefined and item ~= jsonutil.null then + item(state) + else + state:set_value(item) + end + end + if options.initialObject then + assert(type(state.previous) == 'table', "Initial value not an object or array") + end + -- Make sure stack is empty + assert(state.i == 0, "Unclosed elements present") + return state.previous + end + if options.nothrow then + return function(data) + local status, rv = pcall(decoder, data) + if status then + return rv + else + return nil, rv + end + end + end + return decoder +end local function buildDecoder(mode) mode = mode and merge({}, defaultOptions, mode) or defaultOptions + for _, mod in ipairs(loadedModules) do + if mod.mergeOptions then + mod.mergeOptions(mode) + end + end local ignored = mode.unicodeWhitespace and util.unicode_ignored or util.ascii_ignored -- Store 'ignored' in the global options table mode.ignored = ignored - local value_id = util.types.VALUE - local value_type = lpeg.V(value_id) - local object_type = lpeg.V(util.types.OBJECT) - local array_type = lpeg.V(util.types.ARRAY) - local grammar = { - [1] = mode.initialObject and (ignored * (object_type + array_type)) or value_type - } - -- Additional state storage for modules - local state = {} - for _, name in pairs(modulesToLoad) do - local mod = loadedModules[name] - mod.load_types(mode[name], mode, grammar, state) - end - -- HOOK VALUE TYPE WITH WHITESPACE - grammar[value_id] = ignored * grammar[value_id] * ignored - local compiled_grammar = lpeg.P(grammar) * ignored - -- If match-time-capture is supported, implement Cmt workaround for deep captures - if lpeg.Cmt then - if mode.initialObject then - -- Patch the grammar and recompile for VALUE usage - grammar[1] = value_type - state.VALUE_MATCH = lpeg.P(grammar) * ignored - else - state.VALUE_MATCH = compiled_grammar - end - end - -- Only add terminator & pos capture for final grammar since it is expected that there is extra data - -- when using VALUE_MATCH internally - compiled_grammar = compiled_grammar * lpeg.Cp() * -1 - return function(data) - local ret, next_index = lpeg.match(compiled_grammar, data) - assert(nil ~= next_index, "Invalid JSON data") - return ret + --local grammar = { + -- [1] = mode.initialObject and (ignored * (object_type + array_type)) or value_type + --} + local lexer + for _, mod in ipairs(loadedModules) do + local new_lexer = mod.generateLexer(mode) + lexer = lexer and lexer + new_lexer or new_lexer end + return generateDecoder(lexer, mode) end -- Since 'default' is nil, we cannot take map it -local defaultDecoder = buildDecoder(default) +local defaultDecoder = buildDecoder(json_decode.default) local prebuilt_decoders = {} for _, mode in pairs(modes_defined) do - if _M[mode] ~= nil then - prebuilt_decoders[_M[mode]] = buildDecoder(_M[mode]) + if json_decode[mode] ~= nil then + prebuilt_decoders[json_decode[mode]] = buildDecoder(json_decode[mode]) end end @@ -126,8 +144,8 @@ Options: initialObject => whether or not to require the initial object to be a table/array allowUndefined => whether or not to allow undefined values ]] -function getDecoder(mode) - mode = mode == true and strict or mode or default +local function getDecoder(mode) + mode = mode == true and json_decode.strict or mode or json_decode.default local decoder = mode == nil and defaultDecoder or prebuilt_decoders[mode] if decoder then return decoder @@ -135,13 +153,19 @@ function getDecoder(mode) return buildDecoder(mode) end -function decode(data, mode) +local function decode(data, mode) local decoder = getDecoder(mode) return decoder(data) end -local mt = getmetatable(_M) or {} +local mt = {} mt.__call = function(self, ...) return decode(...) end -setmetatable(_M, mt) + +json_decode.getDecoder = getDecoder +json_decode.decode = decode +json_decode.util = util +setmetatable(json_decode, mt) + +return json_decode diff --git a/files/lua/json/decode/array.lua b/files/lua/json/decode/array.lua deleted file mode 100644 index c36dc35..0000000 --- a/files/lua/json/decode/array.lua +++ /dev/null @@ -1,84 +0,0 @@ ---[[ - Licensed according to the included 'LICENSE' document - Author: Thomas Harning Jr -]] -local lpeg = require("lpeg") - -local util = require("json.decode.util") -local jsonutil = require("json.util") - -local table_maxn = require("table").maxn - -local unpack = unpack - -module("json.decode.array") - --- Utility function to help manage slighly sparse arrays -local function processArray(array) - local max_n = table_maxn(array) - -- Only populate 'n' if it is necessary - if #array ~= max_n then - array.n = max_n - end - if jsonutil.InitArray then - array = jsonutil.InitArray(array) or array - end - return array -end - -local defaultOptions = { - trailingComma = true -} - -default = nil -- Let the buildCapture optimization take place -strict = { - trailingComma = false -} - -local function buildCapture(options, global_options, state) - local ignored = global_options.ignored - -- arrayItem == element - local arrayItem = lpeg.V(util.types.VALUE) - -- If match-time capture supported, use it to remove stack limit for JSON - if lpeg.Cmt then - arrayItem = lpeg.Cmt(lpeg.Cp(), function(str, i) - -- Decode one value then return - local END_MARKER = {} - local pattern = - -- Found empty segment - #lpeg.P(']' * lpeg.Cc(END_MARKER) * lpeg.Cp()) - -- Found a value + captured, check for required , or ] + capture next pos - + state.VALUE_MATCH * #(lpeg.P(',') + lpeg.P(']')) * lpeg.Cp() - local capture, i = pattern:match(str, i) - if END_MARKER == capture then - return i - elseif (i == nil and capture == nil) then - return false - else - return i, capture - end - end) - end - local arrayElements = lpeg.Ct(arrayItem * (ignored * lpeg.P(',') * ignored * arrayItem)^0 + 0) / processArray - - options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions - local capture = lpeg.P("[") - capture = capture * ignored - * arrayElements * ignored - if options.trailingComma then - capture = capture * (lpeg.P(",") + 0) * ignored - end - capture = capture * lpeg.P("]") - return capture -end - -function register_types() - util.register_type("ARRAY") -end - -function load_types(options, global_options, grammar, state) - local capture = buildCapture(options, global_options, state) - local array_id = util.types.ARRAY - grammar[array_id] = capture - util.append_grammar_item(grammar, "VALUE", lpeg.V(array_id)) -end diff --git a/files/lua/json/decode/calls.lua b/files/lua/json/decode/calls.lua deleted file mode 100644 index d653df6..0000000 --- a/files/lua/json/decode/calls.lua +++ /dev/null @@ -1,135 +0,0 @@ ---[[ - Licensed according to the included 'LICENSE' document - Author: Thomas Harning Jr -]] -local lpeg = require("lpeg") -local tostring = tostring -local pairs, ipairs = pairs, ipairs -local next, type = next, type -local error = error - -local util = require("json.decode.util") - -local buildCall = require("json.util").buildCall - -local getmetatable = getmetatable - -module("json.decode.calls") - -local defaultOptions = { - defs = nil, - -- By default, do not allow undefined calls to be de-serialized as call objects - allowUndefined = false -} - --- No real default-option handling needed... -default = nil -strict = nil - -local isPattern -if lpeg.type then - function isPattern(value) - return lpeg.type(value) == 'pattern' - end -else - local metaAdd = getmetatable(lpeg.P("")).__add - function isPattern(value) - return getmetatable(value).__add == metaAdd - end -end - -local function buildDefinedCaptures(argumentCapture, defs) - local callCapture - if not defs then return end - for name, func in pairs(defs) do - if type(name) ~= 'string' and not isPattern(name) then - error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern") - end - -- Allow boolean or function to match up w/ encoding permissions - if type(func) ~= 'boolean' and type(func) ~= 'function' then - error("Invalid functionCalls item: " .. name .. " not a function") - end - local nameCallCapture - if type(name) == 'string' then - nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name) - else - -- Name matcher expected to produce a capture - nameCallCapture = name * "(" - end - -- Call func over nameCallCapture and value to permit function receiving name - - -- Process 'func' if it is not a function - if type(func) == 'boolean' then - local allowed = func - func = function(name, ...) - if not allowed then - error("Function call on '" .. name .. "' not permitted") - end - return buildCall(name, ...) - end - else - local inner_func = func - func = function(...) - return (inner_func(...)) - end - end - local newCapture = (nameCallCapture * argumentCapture) / func * ")" - if not callCapture then - callCapture = newCapture - else - callCapture = callCapture + newCapture - end - end - return callCapture -end - -local function buildCapture(options, global_options, state) - if not options -- No ops, don't bother to parse - or not (options.defs and (nil ~= next(options.defs)) or options.allowUndefined) then - return nil - end - -- Allow zero or more arguments separated by commas - local value = lpeg.V(util.types.VALUE) - if lpeg.Cmt then - value = lpeg.Cmt(lpeg.Cp(), function(str, i) - -- Decode one value then return - local END_MARKER = {} - local pattern = - -- Found empty segment - #lpeg.P(')' * lpeg.Cc(END_MARKER) * lpeg.Cp()) - -- Found a value + captured, check for required , or ) + capture next pos - + state.VALUE_MATCH * #(lpeg.P(',') + lpeg.P(')')) * lpeg.Cp() - local capture, i = pattern:match(str, i) - if END_MARKER == capture then - return i - elseif (i == nil and capture == nil) then - return false - else - return i, capture - end - end) - end - local argumentCapture = (value * (lpeg.P(",") * value)^0) + 0 - local callCapture = buildDefinedCaptures(argumentCapture, options.defs) - if options.allowUndefined then - local function func(name, ...) - return buildCall(name, ...) - end - -- Identifier-type-match - local nameCallCapture = lpeg.C(util.identifier) * "(" - local newCapture = (nameCallCapture * argumentCapture) / func * ")" - if not callCapture then - callCapture = newCapture - else - callCapture = callCapture + newCapture - end - end - return callCapture -end - -function load_types(options, global_options, grammar, state) - local capture = buildCapture(options, global_options, state) - if capture then - util.append_grammar_item(grammar, "VALUE", capture) - end -end diff --git a/files/lua/json/decode/composite.lua b/files/lua/json/decode/composite.lua new file mode 100644 index 0000000..0dda1dd --- /dev/null +++ b/files/lua/json/decode/composite.lua @@ -0,0 +1,194 @@ +--[[ + Licensed according to the included 'LICENSE' document + Author: Thomas Harning Jr +]] +local pairs = pairs +local type = type + +local lpeg = require("lpeg") + +local util = require("json.decode.util") +local jsonutil = require("json.util") + +local rawset = rawset + +local assert = assert +local tostring = tostring + +local error = error +local getmetatable = getmetatable + +local _ENV = nil + +local defaultOptions = { + array = { + allowEmptyElement = false, + trailingComma = true + }, + object = { + allowEmptyElement = false, + trailingComma = true, + number = true, + identifier = true, + setObjectKey = rawset + }, + calls = { + allowEmptyElement = false, + defs = nil, + -- By default, do not allow undefined calls to be de-serialized as call objects + allowUndefined = false, + trailingComma = true + } +} + +local modeOptions = { + default = nil, + strict = { + array = { + trailingComma = false + }, + object = { + trailingComma = false, + number = false, + identifier = false + } + } +} + +local function BEGIN_ARRAY(state) + state:push() + state:new_array() +end +local function END_ARRAY(state) + state:end_array() + state:pop() +end + +local function BEGIN_OBJECT(state) + state:push() + state:new_object() +end +local function END_OBJECT(state) + state:end_object() + state:pop() +end + +local function END_CALL(state) + state:end_call() + state:pop() +end + +local function SET_KEY(state) + state:set_key() +end + +local function NEXT_VALUE(state) + state:put_value() +end + +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, true, 'array', defaultOptions, mode and modeOptions[mode]) + jsonutil.doOptionMerge(options, true, 'object', defaultOptions, mode and modeOptions[mode]) + jsonutil.doOptionMerge(options, true, 'calls', defaultOptions, mode and modeOptions[mode]) +end + + +local isPattern +if lpeg.type then + function isPattern(value) + return lpeg.type(value) == 'pattern' + end +else + local metaAdd = getmetatable(lpeg.P("")).__add + function isPattern(value) + return getmetatable(value).__add == metaAdd + end +end + + +local function generateSingleCallLexer(name, func) + if type(name) ~= 'string' and not isPattern(name) then + error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern") + end + -- Allow boolean or function to match up w/ encoding permissions + if type(func) ~= 'boolean' and type(func) ~= 'function' then + error("Invalid functionCalls item: " .. name .. " not a function") + end + local function buildCallCapture(name) + return function(state) + if func == false then + error("Function call on '" .. name .. "' not permitted") + end + state:push() + state:new_call(name, func) + end + end + local nameCallCapture + if type(name) == 'string' then + nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name) / buildCallCapture + else + -- Name matcher expected to produce a capture + nameCallCapture = name * "(" / buildCallCapture + end + -- Call func over nameCallCapture and value to permit function receiving name + return nameCallCapture +end + +local function generateNamedCallLexers(options) + if not options.calls or not options.calls.defs then + return + end + local callCapture + for name, func in pairs(options.calls.defs) do + local newCapture = generateSingleCallLexer(name, func) + if not callCapture then + callCapture = newCapture + else + callCapture = callCapture + newCapture + end + end + return callCapture +end + +local function generateCallLexer(options) + local lexer + local namedCapture = generateNamedCallLexers(options) + if options.calls and options.calls.allowUndefined then + lexer = generateSingleCallLexer(lpeg.C(util.identifier), true) + end + if namedCapture then + lexer = lexer and lexer + namedCapture or namedCapture + end + if lexer then + lexer = lexer + lpeg.P(")") * lpeg.Cc(END_CALL) + end + return lexer +end + +local function generateLexer(options) + local ignored = options.ignored + local array_options, object_options = options.array, options.object + local lexer = + lpeg.P("[") * lpeg.Cc(BEGIN_ARRAY) + + lpeg.P("]") * lpeg.Cc(END_ARRAY) + + lpeg.P("{") * lpeg.Cc(BEGIN_OBJECT) + + lpeg.P("}") * lpeg.Cc(END_OBJECT) + + lpeg.P(":") * lpeg.Cc(SET_KEY) + + lpeg.P(",") * lpeg.Cc(NEXT_VALUE) + if object_options.identifier then + -- Add identifier match w/ validation check that it is in key + lexer = lexer + lpeg.C(util.identifier) * ignored * lpeg.P(":") * lpeg.Cc(SET_KEY) + end + local callLexers = generateCallLexer(options) + if callLexers then + lexer = lexer + callLexers + end + return lexer +end + +local composite = { + mergeOptions = mergeOptions, + generateLexer = generateLexer +} + +return composite diff --git a/files/lua/json/decode/number.lua b/files/lua/json/decode/number.lua index efc7fd6..94ed3b8 100644 --- a/files/lua/json/decode/number.lua +++ b/files/lua/json/decode/number.lua @@ -4,16 +4,19 @@ ]] local lpeg = require("lpeg") local tonumber = tonumber -local merge = require("json.util").merge +local jsonutil = require("json.util") +local merge = jsonutil.merge local util = require("json.decode.util") -module("json.decode.number") +local _ENV = nil local digit = lpeg.R("09") local digits = digit^1 -int = (lpeg.P('-') + 0) * (lpeg.R("19") * digits + digit) -local int = int +-- Illegal octal declaration +local illegal_octal_detect = #(lpeg.P('0') * digits) * util.denied("Octal numbers") + +local int = (lpeg.P('-') + 0) * (lpeg.R("19") * digits + illegal_octal_detect + digit) local frac = lpeg.P('.') * digits @@ -32,8 +35,9 @@ local defaultOptions = { hex = false } -default = nil -- Let the buildCapture optimization take place -strict = { +local modeOptions = {} + +modeOptions.strict = { nan = false, inf = false } @@ -50,36 +54,47 @@ local ninf_value = -1/0 exp: match exponent portion (e1) DEFAULT: nan, inf, frac, exp ]] -local function buildCapture(options) - options = options and merge({}, defaultOptions, options) or defaultOptions +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode]) +end + +local function generateLexer(options) + options = options.number local ret = int if options.frac then ret = ret * (frac + 0) + else + ret = ret * (#frac * util.denied("Fractions", "number.frac") + 0) end if options.exp then ret = ret * (exp + 0) + else + ret = ret * (#exp * util.denied("Exponents", "number.exp") + 0) end if options.hex then ret = hex + ret + else + ret = #hex * util.denied("Hexadecimal", "number.hex") + ret end -- Capture number now ret = ret / tonumber if options.nan then ret = ret + nan / function() return nan_value end + else + ret = ret + #nan * util.denied("NaN", "number.nan") end if options.inf then ret = ret + ninf / function() return ninf_value end + inf / function() return inf_value end + else + ret = ret + (#ninf + #inf) * util.denied("+/-Inf", "number.inf") end return ret end -function register_types() - util.register_type("INTEGER") -end +local number = { + int = int, + mergeOptions = mergeOptions, + generateLexer = generateLexer +} -function load_types(options, global_options, grammar) - local integer_id = util.types.INTEGER - local capture = buildCapture(options) - util.append_grammar_item(grammar, "VALUE", capture) - grammar[integer_id] = int / tonumber -end +return number diff --git a/files/lua/json/decode/object.lua b/files/lua/json/decode/object.lua deleted file mode 100644 index ad1beac..0000000 --- a/files/lua/json/decode/object.lua +++ /dev/null @@ -1,125 +0,0 @@ ---[[ - Licensed according to the included 'LICENSE' document - Author: Thomas Harning Jr -]] -local lpeg = require("lpeg") - -local util = require("json.decode.util") -local merge = require("json.util").merge - -local tonumber = tonumber -local unpack = unpack -local print = print -local tostring = tostring - -local rawset = rawset - -module("json.decode.object") - --- BEGIN LPEG < 0.9 SUPPORT -local initObject, applyObjectKey -if not (lpeg.Cg and lpeg.Cf and lpeg.Ct) then - function initObject() - return {} - end - function applyObjectKey(tab, key, val) - tab[key] = val - return tab - end -end --- END LPEG < 0.9 SUPPORT - -local defaultOptions = { - number = true, - identifier = true, - trailingComma = true -} - -default = nil -- Let the buildCapture optimization take place - -strict = { - number = false, - identifier = false, - trailingComma = false -} - -local function buildItemSequence(objectItem, ignored) - return (objectItem * (ignored * lpeg.P(",") * ignored * objectItem)^0) + 0 -end - -local function buildCapture(options, global_options, state) - local ignored = global_options.ignored - local string_type = lpeg.V(util.types.STRING) - local integer_type = lpeg.V(util.types.INTEGER) - local value_type = lpeg.V(util.types.VALUE) - -- If match-time capture supported, use it to remove stack limit for JSON - if lpeg.Cmt then - value_type = lpeg.Cmt(lpeg.Cp(), function(str, i) - -- Decode one value then return - local END_MARKER = {} - local pattern = - -- Found empty segment - #lpeg.P('}' * lpeg.Cc(END_MARKER) * lpeg.Cp()) - -- Found a value + captured, check for required , or } + capture next pos - + state.VALUE_MATCH * #(lpeg.P(',') + lpeg.P('}')) * lpeg.Cp() - local capture, i = pattern:match(str, i) - if END_MARKER == capture then - return i - elseif (i == nil and capture == nil) then - return false - else - return i, capture - end - end) - end - - - options = options and merge({}, defaultOptions, options) or defaultOptions - local key = string_type - if options.identifier then - key = key + lpeg.C(util.identifier) - end - if options.number then - key = key + integer_type - end - local objectItems - local objectItem = (key * ignored * lpeg.P(":") * ignored * value_type) - -- BEGIN LPEG < 0.9 SUPPORT - if not (lpeg.Cg and lpeg.Cf and lpeg.Ct) then - local set_key = applyObjectKey - if options.setObjectKey then - local setObjectKey = options.setObjectKey - set_key = function(tab, key, val) - setObjectKey(tab, key, val) - return tab - end - end - - objectItems = buildItemSequence(objectItem / set_key, ignored) - objectItems = lpeg.Ca(lpeg.Cc(false) / initObject * objectItems) - -- END LPEG < 0.9 SUPPORT - else - objectItems = buildItemSequence(lpeg.Cg(objectItem), ignored) - objectItems = lpeg.Cf(lpeg.Ct(0) * objectItems, options.setObjectKey or rawset) - end - - - local capture = lpeg.P("{") * ignored - capture = capture * objectItems * ignored - if options.trailingComma then - capture = capture * (lpeg.P(",") + 0) * ignored - end - capture = capture * lpeg.P("}") - return capture -end - -function register_types() - util.register_type("OBJECT") -end - -function load_types(options, global_options, grammar, state) - local capture = buildCapture(options, global_options, state) - local object_id = util.types.OBJECT - grammar[object_id] = capture - util.append_grammar_item(grammar, "VALUE", lpeg.V(object_id)) -end diff --git a/files/lua/json/decode/others.lua b/files/lua/json/decode/others.lua index 01d60db..9fab7a8 100644 --- a/files/lua/json/decode/others.lua +++ b/files/lua/json/decode/others.lua @@ -4,12 +4,12 @@ ]] local lpeg = require("lpeg") local jsonutil = require("json.util") +local merge = jsonutil.merge local util = require("json.decode.util") -local rawset = rawset - -- Container module for other JavaScript types (bool, null, undefined) -module("json.decode.others") + +local _ENV = nil -- For null and undefined, use the util.null value to preserve null-ness local booleanCapture = @@ -22,33 +22,41 @@ local undefinedCapture = lpeg.P("undefined") local defaultOptions = { allowUndefined = true, null = jsonutil.null, - undefined = jsonutil.undefined, - setObjectKey = rawset + undefined = jsonutil.undefined } -default = nil -- Let the buildCapture optimization take place -simple = { +local modeOptions = {} + +modeOptions.simple = { null = false, -- Mapped to nil undefined = false -- Mapped to nil } -strict = { +modeOptions.strict = { allowUndefined = false } -local function buildCapture(options) +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode]) +end + +local function generateLexer(options) -- The 'or nil' clause allows false to map to a nil value since 'nil' cannot be merged - options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions + options = options.others local valueCapture = ( booleanCapture + nullCapture * lpeg.Cc(options.null or nil) ) if options.allowUndefined then valueCapture = valueCapture + undefinedCapture * lpeg.Cc(options.undefined or nil) + else + valueCapture = valueCapture + #undefinedCapture * util.denied("undefined", "others.allowUndefined") end return valueCapture end -function load_types(options, global_options, grammar) - local capture = buildCapture(options) - util.append_grammar_item(grammar, "VALUE", capture) -end +local others = { + mergeOptions = mergeOptions, + generateLexer = generateLexer +} + +return others diff --git a/files/lua/json/decode/state.lua b/files/lua/json/decode/state.lua new file mode 100644 index 0000000..a269d7c --- /dev/null +++ b/files/lua/json/decode/state.lua @@ -0,0 +1,214 @@ +--[[ + Licensed according to the included 'LICENSE' document + Author: Thomas Harning Jr +]] + +local setmetatable = setmetatable +local jsonutil = require("json.util") +local assert = assert +local type = type +local next = next +local unpack = require("table").unpack or unpack + +local _ENV = nil + +local state_ops = {} +local state_mt = { + __index = state_ops +} + +function state_ops.pop(self) + self.previous_set = true + self.previous = self.active + local i = self.i + -- Load in this array into the active item + self.active = self.stack[i] + self.active_state = self.state_stack[i] + self.active_key = self.key_stack[i] + self.stack[i] = nil + self.state_stack[i] = nil + self.key_stack[i] = nil + + self.i = i - 1 +end + +function state_ops.push(self) + local i = self.i + 1 + self.i = i + + self.stack[i] = self.active + self.state_stack[i] = self.active_state + self.key_stack[i] = self.active_key +end + +function state_ops.put_object_value(self, trailing) + local object_options = self.options.object + if trailing and object_options.trailingComma then + if not self.active_key then + return + end + end + assert(self.active_key, "Missing key value") + object_options.setObjectKey(self.active, self.active_key, self:grab_value(object_options.allowEmptyElement)) + self.active_key = nil +end + +function state_ops.put_array_value(self, trailing) + local array_options = self.options.array + -- Safety check + if trailing and not self.previous_set and array_options.trailingComma then + return + end + local new_index = self.active_state + 1 + self.active_state = new_index + self.active[new_index] = self:grab_value(array_options.allowEmptyElement) +end + +function state_ops.put_call_value(self, trailing) + local call_options = self.options.calls + -- Safety check + if trailing and not self.previous_set and call_options.trailingComma then + return + end + local new_index = self.active_state + 1 + self.active_state = new_index + self.active[new_index] = self:grab_value(call_options.allowEmptyElement) +end + +function state_ops.put_value(self, trailing) + if self.active_state == 'object' then + self:put_object_value(trailing) + elseif self.active.func then + self:put_call_value(trailing) + else + self:put_array_value(trailing) + end +end + +function state_ops.new_array(self) + local new_array = {} + if jsonutil.InitArray then + new_array = jsonutil.InitArray(new_array) or new_array + end + self.active = new_array + self.active_state = 0 + self.active_key = nil + self:unset_value() +end + +function state_ops.end_array(self) + if self.previous_set or self.active_state ~= 0 then + -- Not an empty array + self:put_value(true) + end + if self.active_state ~= #self.active then + -- Store the length in + self.active.n = self.active_state + end +end + +function state_ops.new_object(self) + local new_object = {} + self.active = new_object + self.active_state = 'object' + self.active_key = nil + self:unset_value() +end + +function state_ops.end_object(self) + if self.active_key or self.previous_set or next(self.active) then + -- Not an empty object + self:put_value(true) + end +end + +function state_ops.new_call(self, name, func) + -- TODO setup properly + local new_call = {} + new_call.name = name + new_call.func = func + self.active = new_call + self.active_state = 0 + self.active_key = nil + self:unset_value() +end + +function state_ops.end_call(self) + if self.previous_set or self.active_state ~= 0 then + -- Not an empty array + self:put_value(true) + end + if self.active_state ~= #self.active then + -- Store the length in + self.active.n = self.active_state + end + local func = self.active.func + if func == true then + func = jsonutil.buildCall + end + self.active = func(self.active.name, unpack(self.active, 1, self.active.n or #self.active)) +end + + +function state_ops.unset_value(self) + self.previous_set = false + self.previous = nil +end + +function state_ops.grab_value(self, allowEmptyValue) + if not self.previous_set and allowEmptyValue then + -- Calculate an appropriate empty-value + return self.emptyValue + end + assert(self.previous_set, "Previous value not set") + self.previous_set = false + return self.previous +end + +function state_ops.set_value(self, value) + assert(not self.previous_set, "Value set when one already in slot") + self.previous_set = true + self.previous = value +end + +function state_ops.set_key(self) + assert(self.active_state == 'object', "Cannot set key on array") + local value = self:grab_value() + local value_type = type(value) + if self.options.object.number then + assert(value_type == 'string' or value_type == 'number', "As configured, a key must be a number or string") + else + assert(value_type == 'string', "As configured, a key must be a string") + end + self.active_key = value +end + + +local function create(options) + local emptyValue + -- Calculate an empty value up front + if options.others.allowUndefined then + emptyValue = options.others.undefined or nil + else + emptyValue = options.others.null or nil + end + local ret = { + options = options, + stack = {}, + state_stack = {}, + key_stack = {}, + i = 0, + active = nil, + active_key = nil, + previous = nil, + active_state = nil, + emptyValue = emptyValue + } + return setmetatable(ret, state_mt) +end + +local state = { + create = create +} + +return state diff --git a/files/lua/json/decode/strings.lua b/files/lua/json/decode/strings.lua index f092558..4272f29 100644 --- a/files/lua/json/decode/strings.lua +++ b/files/lua/json/decode/strings.lua @@ -3,8 +3,9 @@ Author: Thomas Harning Jr ]] local lpeg = require("lpeg") +local jsonutil = require("json.util") local util = require("json.decode.util") -local merge = require("json.util").merge +local merge = jsonutil.merge local tonumber = tonumber local string_char = require("string").char @@ -12,14 +13,16 @@ local floor = require("math").floor local table_concat = require("table").concat local error = error -module("json.decode.strings") + +local _ENV = nil + local function get_error(item) local fmt_string = item .. " in string [%q] @ %i:%i" - return function(data, index) + return lpeg.P(function(data, index) local line, line_index, bad_char, last_line = util.get_invalid_character_info(data, index) local err = fmt_string:format(bad_char, line, line_index) error(err) - end + end) * 1 end local bad_unicode = get_error("Illegal unicode escape") @@ -64,8 +67,8 @@ local function decodeX(code) end local doSimpleSub = lpeg.C(lpeg.S("'\"\\/bfnrtvz")) / knownReplacements -local doUniSub = lpeg.P('u') * (lpeg.C(util.hexpair) * lpeg.C(util.hexpair) + lpeg.P(bad_unicode)) -local doXSub = lpeg.P('x') * (lpeg.C(util.hexpair) + lpeg.P(bad_hex)) +local doUniSub = lpeg.P('u') * (lpeg.C(util.hexpair) * lpeg.C(util.hexpair) + bad_unicode) +local doXSub = lpeg.P('x') * (lpeg.C(util.hexpair) + bad_hex) local defaultOptions = { badChars = '', @@ -75,24 +78,28 @@ local defaultOptions = { strict_quotes = false } -default = nil -- Let the buildCapture optimization take place +local modeOptions = {} -strict = { +modeOptions.strict = { badChars = '\b\f\n\r\t\v', additionalEscapes = false, -- no additional escapes escapeCheck = #lpeg.S('bfnrtv/\\"u'), --only these chars are allowed to be escaped strict_quotes = true } +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode]) +end + local function buildCaptureString(quote, badChars, escapeMatch) local captureChar = (1 - lpeg.S("\\" .. badChars .. quote)) + (lpeg.P("\\") / "" * escapeMatch) - captureChar = captureChar + (-#lpeg.P(quote) * lpeg.P(bad_character)) - local captureString = captureChar^0 + -- During error, force end + local captureString = captureChar^0 + (-#lpeg.P(quote) * bad_character + -1) return lpeg.P(quote) * lpeg.Cs(captureString) * lpeg.P(quote) end -local function buildCapture(options) - options = options and merge({}, defaultOptions, options) or defaultOptions +local function generateLexer(options) + options = options.strings local quotes = { '"' } if not options.strict_quotes then quotes[#quotes + 1] = "'" @@ -100,11 +107,11 @@ local function buildCapture(options) local escapeMatch = doSimpleSub escapeMatch = escapeMatch + doXSub / decodeX escapeMatch = escapeMatch + doUniSub / options.decodeUnicode - if options.additionalEscapes then - escapeMatch = escapeMatch + options.additionalEscapes - end if options.escapeCheck then - escapeMatch = options.escapeCheck * escapeMatch + lpeg.P(bad_escape) + escapeMatch = options.escapeCheck * escapeMatch + bad_escape + end + if options.additionalEscapes then + escapeMatch = options.additionalEscapes + escapeMatch end local captureString for i = 1, #quotes do @@ -118,13 +125,9 @@ local function buildCapture(options) return captureString end -function register_types() - util.register_type("STRING") -end +local strings = { + mergeOptions = mergeOptions, + generateLexer = generateLexer +} -function load_types(options, global_options, grammar) - local capture = buildCapture(options) - local string_id = util.types.STRING - grammar[string_id] = capture - util.append_grammar_item(grammar, "VALUE", lpeg.V(string_id)) -end +return strings diff --git a/files/lua/json/decode/util.lua b/files/lua/json/decode/util.lua index f34335c..2493bf3 100644 --- a/files/lua/json/decode/util.lua +++ b/files/lua/json/decode/util.lua @@ -8,14 +8,50 @@ local pairs, ipairs = pairs, ipairs local tonumber = tonumber local string_char = require("string").char local rawset = rawset +local jsonutil = require("json.util") local error = error local setmetatable = setmetatable -module("json.decode.util") +local table_concat = require("table").concat + +local merge = require("json.util").merge + +local _ENV = nil + +local function get_invalid_character_info(input, index) + local parsed = input:sub(1, index) + local bad_character = input:sub(index, index) + local _, line_number = parsed:gsub('\n',{}) + local last_line = parsed:match("\n([^\n]+.)$") or parsed + return line_number, #last_line, bad_character, last_line +end + +local function build_report(msg) + local fmt = msg:gsub("%%", "%%%%") .. " @ character: %i %i:%i [%s] line:\n%s" + return lpeg.P(function(data, pos) + local line, line_index, bad_char, last_line = get_invalid_character_info(data, pos) + local text = fmt:format(pos, line, line_index, bad_char, last_line) + error(text) + end) * 1 +end +local function unexpected() + local msg = "unexpected character" + return build_report(msg) +end +local function denied(item, option) + local msg + if option then + msg = ("'%s' denied by option set '%s'"):format(item, option) + else + msg = ("'%s' denied"):format(item) + end + return build_report(msg) +end -- 09, 0A, 0B, 0C, 0D, 20 -ascii_space = lpeg.S("\t\n\v\f\r ") +local ascii_space = lpeg.S("\t\n\v\f\r ") +local unicode_space do local chr = string_char local u_space = ascii_space @@ -37,62 +73,49 @@ do u_space = u_space + lpeg.P(chr(0xE3, 0x80, 0x80)) -- BOM \uFEFF u_space = u_space + lpeg.P(chr(0xEF, 0xBB, 0xBF)) - _M.unicode_space = u_space + unicode_space = u_space end -identifier = lpeg.R("AZ","az","__") * lpeg.R("AZ","az", "__", "09") ^0 +local identifier = lpeg.R("AZ","az","__") * lpeg.R("AZ","az", "__", "09") ^0 -hex = lpeg.R("09","AF","af") -hexpair = hex * hex +local hex = lpeg.R("09","AF","af") +local hexpair = hex * hex -comments = { +local comments = { cpp = lpeg.P("//") * (1 - lpeg.P("\n"))^0 * lpeg.P("\n"), c = lpeg.P("/*") * (1 - lpeg.P("*/"))^0 * lpeg.P("*/") } -comment = comments.cpp + comments.c - -ascii_ignored = (ascii_space + comment)^0 - -unicode_ignored = (unicode_space + comment)^0 +local comment = comments.cpp + comments.c -local types = setmetatable({false}, { - __index = function(self, k) - error("Unknown type: " .. k) - end -}) - -function register_type(name) - types[#types + 1] = name - types[name] = #types - return #types -end +local ascii_ignored = (ascii_space + comment)^0 -_M.types = types - -function append_grammar_item(grammar, name, capture) - local id = types[name] - local original = grammar[id] - if original then - grammar[id] = original + capture - else - grammar[id] = capture - end -end +local unicode_ignored = (unicode_space + comment)^0 -- Parse the lpeg version skipping patch-values -- LPEG <= 0.7 have no version value... so 0.7 is value -DecimalLpegVersion = lpeg.version and tonumber(lpeg.version():match("^(%d+%.%d+)")) or 0.7 +local DecimalLpegVersion = lpeg.version and tonumber(lpeg.version():match("^(%d+%.%d+)")) or 0.7 -function get_invalid_character_info(input, index) - local parsed = input:sub(1, index) - local bad_character = input:sub(index, index) - local _, line_number = parsed:gsub('\n',{}) - local last_line = parsed:match("\n([^\n]+.)$") or parsed - return line_number, #last_line, bad_character, last_line -end - -function setObjectKeyForceNumber(t, key, value) +local function setObjectKeyForceNumber(t, key, value) key = tonumber(key) or key return rawset(t, key, value) end + +local util = { + unexpected = unexpected, + denied = denied, + ascii_space = ascii_space, + unicode_space = unicode_space, + identifier = identifier, + hex = hex, + hexpair = hexpair, + comments = comments, + comment = comment, + ascii_ignored = ascii_ignored, + unicode_ignored = unicode_ignored, + DecimalLpegVersion = DecimalLpegVersion, + get_invalid_character_info = get_invalid_character_info, + setObjectKeyForceNumber = setObjectKeyForceNumber +} + +return util diff --git a/files/lua/json/encode.lua b/files/lua/json/encode.lua index 8666ea4..5a13adc 100644 --- a/files/lua/json/encode.lua +++ b/files/lua/json/encode.lua @@ -5,7 +5,6 @@ local type = type local assert, error = assert, error local getmetatable, setmetatable = getmetatable, setmetatable -local util = require("json.util") local ipairs, pairs = ipairs, pairs local require = require @@ -15,7 +14,7 @@ local output = require("json.encode.output") local util = require("json.util") local util_merge, isCall = util.merge, util.isCall -module("json.encode") +local _ENV = nil --[[ List of encoding modules to load. @@ -33,33 +32,33 @@ local modulesToLoad = { -- Modules that have been loaded local loadedModules = {} --- Default configuration options to apply -local defaultOptions = {} +local json_encode = {} + -- Configuration bases for client apps -default = nil -strict = { +local modes_defined = { "default", "strict" } + +json_encode.default = {} +json_encode.strict = { initialObject = true -- Require an object at the root } -- For each module, load it and its defaults for _,name in ipairs(modulesToLoad) do local mod = require("json.encode." .. name) - defaultOptions[name] = mod.default - strict[name] = mod.strict + if mod.mergeOptions then + for _, mode in pairs(modes_defined) do + mod.mergeOptions(json_encode[mode], mode) + end + end loadedModules[name] = mod end --- Merges values, assumes all tables are arrays, inner values flattened, optionally constructing output -local function flattenOutput(out, values) - out = not out and {} or type(out) == 'table' and out or {out} - if type(values) == 'table' then - for _, v in ipairs(values) do - out[#out + 1] = v - end - else - out[#out + 1] = values - end - return out +-- NOTE: Nested not found, so assume unsupported until use case arises +local function flattenOutput(out, value) + assert(type(value) ~= 'table') + out = out or {} + out[#out + 1] = value + return out end -- Prepares the encoding map from the already provided modules and new config @@ -111,8 +110,8 @@ end the initial encoder is responsible for initializing state State has at least these values configured: encode, check_unique, already_encoded ]] -function getEncoder(options) - options = options and util_merge({}, defaultOptions, options) or defaultOptions +function json_encode.getEncoder(options) + options = options and util_merge({}, json_encode.default, options) or json_encode.default local encode = getBaseEncoder(options) local function initialEncode(value) @@ -148,12 +147,15 @@ end check_unique -- used by inner encoders to make sure value is unique already_encoded -- used to unmark a value as unique ]] -function encode(data, options) - return getEncoder(options)(data) +function json_encode.encode(data, options) + return json_encode.getEncoder(options)(data) end -local mt = getmetatable(_M) or {} +local mt = {} mt.__call = function(self, ...) - return encode(...) + return json_encode.encode(...) end -setmetatable(_M, mt) + +setmetatable(json_encode, mt) + +return json_encode diff --git a/files/lua/json/encode/array.lua b/files/lua/json/encode/array.lua index 45e300c..3744409 100644 --- a/files/lua/json/encode/array.lua +++ b/files/lua/json/encode/array.lua @@ -13,17 +13,20 @@ local math = require("math") local table_concat = table.concat local math_floor, math_modf = math.floor, math.modf -local util_merge = require("json.util").merge -local util_IsArray = require("json.util").IsArray +local jsonutil = require("json.util") +local util_IsArray = jsonutil.IsArray -module("json.encode.array") +local _ENV = nil local defaultOptions = { isArray = util_IsArray } -default = nil -strict = nil +local modeOptions = {} + +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'array', defaultOptions, mode and modeOptions[mode]) +end --[[ Utility function to determine whether a table is an array or not. @@ -34,7 +37,7 @@ strict = nil before it) * It is a contiguous list of values with zero string-based keys ]] -function isArray(val, options) +local function isArray(val, options) local externalIsArray = options and options.isArray if externalIsArray then @@ -72,8 +75,8 @@ local function unmarkAfterEncode(tab, state, ...) state.already_encoded[tab] = nil return ... end -function getEncoder(options) - options = options and util_merge({}, defaultOptions, options) or defaultOptions +local function getEncoder(options) + options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions local function encodeArray(tab, state) if not isArray(tab, options) then return false @@ -97,3 +100,11 @@ function getEncoder(options) end return { table = encodeArray } end + +local array = { + mergeOptions = mergeOptions, + isArray = isArray, + getEncoder = getEncoder +} + +return array diff --git a/files/lua/json/encode/calls.lua b/files/lua/json/encode/calls.lua index c1cc646..11dddfe 100644 --- a/files/lua/json/encode/calls.lua +++ b/files/lua/json/encode/calls.lua @@ -2,8 +2,6 @@ Licensed according to the included 'LICENSE' document Author: Thomas Harning Jr ]] -local jsonutil = require("json.util") - local table = require("table") local table_concat = table.concat @@ -11,19 +9,21 @@ local select = select local getmetatable, setmetatable = getmetatable, setmetatable local assert = assert -local util = require("json.util") - -local util_merge, isCall, decodeCall = util.merge, util.isCall, util.decodeCall +local jsonutil = require("json.util") -module("json.encode.calls") +local isCall, decodeCall = jsonutil.isCall, jsonutil.decodeCall +local _ENV = nil local defaultOptions = { } -- No real default-option handling needed... -default = nil -strict = nil +local modeOptions = {} + +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'calls', defaultOptions, mode and modeOptions[mode]) +end --[[ @@ -32,8 +32,8 @@ strict = nil name == name of the function call parameters == array of parameters to encode ]] -function getEncoder(options) - options = options and util_merge({}, defaultOptions, options) or defaultOptions +local function getEncoder(options) + options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions local function encodeCall(value, state) if not isCall(value) then return false @@ -59,3 +59,10 @@ function getEncoder(options) ['function'] = encodeCall } end + +local calls = { + mergeOptions = mergeOptions, + getEncoder = getEncoder +} + +return calls diff --git a/files/lua/json/encode/number.lua b/files/lua/json/encode/number.lua index 62d5765..290b440 100644 --- a/files/lua/json/encode/number.lua +++ b/files/lua/json/encode/number.lua @@ -4,22 +4,27 @@ ]] local tostring = tostring local assert = assert -local util = require("json.util") +local jsonutil = require("json.util") local huge = require("math").huge -module("json.encode.number") +local _ENV = nil local defaultOptions = { nan = true, inf = true } -default = nil -- Let the buildCapture optimization take place -strict = { +local modeOptions = {} +modeOptions.strict = { nan = false, inf = false } +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode]) +end + + local function encodeNumber(number, options) if number ~= number then assert(options.nan, "Invalid number: NaN not enabled") @@ -36,11 +41,18 @@ local function encodeNumber(number, options) return tostring(number) end -function getEncoder(options) - options = options and util.merge({}, defaultOptions, options) or defaultOptions +local function getEncoder(options) + options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions return { number = function(number, state) return encodeNumber(number, options) end } end + +local number = { + mergeOptions = mergeOptions, + getEncoder = getEncoder +} + +return number diff --git a/files/lua/json/encode/object.lua b/files/lua/json/encode/object.lua index 796347e..4716d52 100644 --- a/files/lua/json/encode/object.lua +++ b/files/lua/json/encode/object.lua @@ -9,15 +9,18 @@ local type = type local tostring = tostring local table_concat = require("table").concat -local util_merge = require("json.util").merge +local jsonutil = require("json.util") -module("json.encode.object") +local _ENV = nil local defaultOptions = { } -default = nil -strict = nil +local modeOptions = {} + +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'object', defaultOptions, mode and modeOptions[mode]) +end --[[ Cleanup function to unmark a value as in the encoding process and return @@ -57,11 +60,18 @@ local function encodeTable(tab, options, state) return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '{', '}', nil, tab, encode, state)) end -function getEncoder(options) - options = options and util_merge({}, defaultOptions, options) or defaultOptions +local function getEncoder(options) + options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions return { table = function(tab, state) return encodeTable(tab, options, state) end } end + +local object = { + mergeOptions = mergeOptions, + getEncoder = getEncoder +} + +return object diff --git a/files/lua/json/encode/others.lua b/files/lua/json/encode/others.lua index ea2c88f..b527044 100644 --- a/files/lua/json/encode/others.lua +++ b/files/lua/json/encode/others.lua @@ -6,13 +6,12 @@ local tostring = tostring local assert = assert local jsonutil = require("json.util") -local util_merge = require("json.util").merge local type = type -module("json.encode.others") +local _ENV = nil -- Shortcut that works -encodeBoolean = tostring +local encodeBoolean = tostring local defaultOptions = { allowUndefined = true, @@ -20,13 +19,17 @@ local defaultOptions = { undefined = jsonutil.undefined } -default = nil -- Let the buildCapture optimization take place -strict = { +local modeOptions = {} + +modeOptions.strict = { allowUndefined = false } -function getEncoder(options) - options = options and util_merge({}, defaultOptions, options) or defaultOptions +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode]) +end +local function getEncoder(options) + options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions local function encodeOthers(value, state) if value == options.null then return 'null' @@ -53,3 +56,11 @@ function getEncoder(options) end return ret end + +local others = { + encodeBoolean = encodeBoolean, + mergeOptions = mergeOptions, + getEncoder = getEncoder +} + +return others diff --git a/files/lua/json/encode/output.lua b/files/lua/json/encode/output.lua index a5dbd79..8293b62 100644 --- a/files/lua/json/encode/output.lua +++ b/files/lua/json/encode/output.lua @@ -5,7 +5,7 @@ local type = type local assert, error = assert, error local table_concat = require("table").concat -local loadstring = loadstring +local loadstring = loadstring or load local io = require("io") @@ -13,7 +13,7 @@ local setmetatable = setmetatable local output_utility = require("json.encode.output_utility") -module("json.encode.output") +local _ENV = nil local tableCompositeCache = setmetatable({}, {__mode = 'v'}) @@ -38,7 +38,7 @@ local function defaultTableCompositeWriter(nextValues, beginValue, closeValue, i end -- no 'simple' as default action is just to return the value -function getDefault() +local function getDefault() return { composite = defaultTableCompositeWriter } end @@ -77,8 +77,15 @@ local function buildIoWriter(output) end return { composite = ioWriter, simple = ioSimpleWriter } end -function getIoWriter(output) +local function getIoWriter(output) return function() return buildIoWriter(output) end end + +local output = { + getDefault = getDefault, + getIoWriter = getIoWriter +} + +return output diff --git a/files/lua/json/encode/output_utility.lua b/files/lua/json/encode/output_utility.lua index b8d3573..b6607d1 100644 --- a/files/lua/json/encode/output_utility.lua +++ b/files/lua/json/encode/output_utility.lua @@ -3,9 +3,9 @@ Author: Thomas Harning Jr ]] local setmetatable = setmetatable -local assert, loadstring = assert, loadstring +local assert, loadstring = assert, loadstring or load -module("json.encode.output_utility") +local _ENV = nil -- Key == weak, if main key goes away, then cache cleared local outputCache = setmetatable({}, {__mode = 'k'}) @@ -33,7 +33,7 @@ local function buildFunction(nextValues, innerValue, valueWriter, innerWriter) return assert(loadstring(functionCode))() end -function prepareEncoder(cacheKey, nextValues, innerValue, valueWriter, innerWriter) +local function prepareEncoder(cacheKey, nextValues, innerValue, valueWriter, innerWriter) local cache = outputCache[cacheKey] if not cache then cache = {} @@ -46,3 +46,9 @@ function prepareEncoder(cacheKey, nextValues, innerValue, valueWriter, innerWrit end return fun end + +local output_utility = { + prepareEncoder = prepareEncoder +} + +return output_utility diff --git a/files/lua/json/encode/strings.lua b/files/lua/json/encode/strings.lua index 933ae4c..09d85a9 100644 --- a/files/lua/json/encode/strings.lua +++ b/files/lua/json/encode/strings.lua @@ -5,8 +5,10 @@ local string_char = require("string").char local pairs = pairs -local util_merge = require("json.util").merge -module("json.encode.strings") +local jsonutil = require("json.util") +local util_merge = jsonutil.merge + +local _ENV = nil local normalEncodingMap = { ['"'] = '\\"', @@ -49,10 +51,13 @@ local defaultOptions = { encodeSetAppend = nil -- Chars to append to the default set } -default = nil -strict = nil +local modeOptions = {} + +local function mergeOptions(options, mode) + jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode]) +end -function getEncoder(options) +local function getEncoder(options) options = options and util_merge({}, defaultOptions, options) or defaultOptions local encodeSet = options.encodeSet if options.encodeSetAppend then @@ -74,3 +79,10 @@ function getEncoder(options) string = encodeString } end + +local strings = { + mergeOptions = mergeOptions, + getEncoder = getEncoder +} + +return strings diff --git a/files/lua/json/util.lua b/files/lua/json/util.lua index 28e47ea..a4599db 100644 --- a/files/lua/json/util.lua +++ b/files/lua/json/util.lua @@ -9,13 +9,14 @@ local pairs = pairs local getmetatable, setmetatable = getmetatable, setmetatable local select = select -module("json.util") +local _ENV = nil + local function foreach(tab, func) for k, v in pairs(tab) do func(k,v) end end -function printValue(tab, name) +local function printValue(tab, name) local parsed = {} local function doPrint(key, value, space) space = space or '' @@ -38,7 +39,7 @@ function printValue(tab, name) doPrint(name, tab) end -function clone(t) +local function clone(t) local ret = {} for k,v in pairs(t) do ret[k] = v @@ -46,24 +47,37 @@ function clone(t) return ret end -local function merge(t, from, ...) - if not from then +local function inner_merge(t, remaining, from, ...) + if remaining == 0 then return t end - for k,v in pairs(from) do - t[k] = v + if from then + for k,v in pairs(from) do + t[k] = v + end end - return merge(t, ...) + return inner_merge(t, remaining - 1, ...) +end + +--[[* + Shallow-merges tables in order onto the first table. + + @param t table to merge entries onto + @param ... sequence of 0 or more tables to merge onto 't' + + @returns table 't' from input +]] +local function merge(t, ...) + return inner_merge(t, select('#', ...), ...) end -_M.merge = merge -- Function to insert nulls into the JSON stream -function null() +local function null() return null end -- Marker for 'undefined' values -function undefined() +local function undefined() return undefined end @@ -74,27 +88,28 @@ local ArrayMT = {} Or false if it has no array component at all Otherwise nil to get the normal detection component working ]] -function IsArray(value) +local function IsArray(value) if type(value) ~= 'table' then return false end - local ret = getmetatable(value) == ArrayMT + local meta = getmetatable(value) + local ret = meta == ArrayMT or (meta ~= nil and meta.__is_luajson_array) if not ret then if #value == 0 then return false end else return ret end end -function InitArray(array) +local function InitArray(array) setmetatable(array, ArrayMT) return array end local CallMT = {} -function isCall(value) +local function isCall(value) return CallMT == getmetatable(value) end -function buildCall(name, ...) +local function buildCall(name, ...) local callData = { name = name, parameters = {n = select('#', ...), ...} @@ -102,7 +117,36 @@ function buildCall(name, ...) return setmetatable(callData, CallMT) end -function decodeCall(callData) +local function decodeCall(callData) if not isCall(callData) then return nil end return callData.name, callData.parameters end + +local function doOptionMerge(options, nested, name, defaultOptions, modeOptions) + if nested then + modeOptions = modeOptions and modeOptions[name] + defaultOptions = defaultOptions and defaultOptions[name] + end + options[name] = merge( + {}, + defaultOptions, + modeOptions, + options[name] + ) +end + +local json_util = { + printValue = printValue, + clone = clone, + merge = merge, + null = null, + undefined = undefined, + IsArray = IsArray, + InitArray = InitArray, + isCall = isCall, + buildCall = buildCall, + decodeCall = decodeCall, + doOptionMerge = doOptionMerge +} + +return json_util diff --git a/files/lua/re.lua b/files/lua/re.lua index 269356a..1fb9fa9 100644 --- a/files/lua/re.lua +++ b/files/lua/re.lua @@ -1,4 +1,7 @@ --- $Id: re.lua,v 1.38 2010/11/03 17:21:07 roberto Exp $ +-- +-- Copyright 2007-2023, Lua.org & PUC-Rio (see 'lpeg.html' for license) +-- written by Roberto Ierusalimschy +-- -- imported functions and modules local tonumber, type, print, error = tonumber, type, print, error @@ -10,14 +13,14 @@ local m = require"lpeg" -- on 'mm' local mm = m --- pattern's metatable +-- patterns' metatable local mt = getmetatable(mm.P(0)) +local version = _VERSION -- No more global accesses after this point -local version = _VERSION -if version == "Lua 5.2" then _ENV = nil end +_ENV = nil -- does no harm in Lua 5.1 local any = m.P(1) @@ -71,13 +74,6 @@ updatelocale() local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) -local function getdef (id, Defs) - local c = Defs and Defs[id] - if not c then error("undefined name: " .. id) end - return c -end - - local function patt_error (s, i) local msg = (#s < i + 20) and s:sub(i) or s:sub(i,i+20) .. "..." @@ -104,17 +100,31 @@ end local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 -local name = m.R("AZ", "az") * m.R("AZ", "az", "__", "09")^0 +local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0 local arrow = S * "<-" -local exp_follow = m.P"/" + ")" + "}" + ":}" + "~}" + (name * arrow) + -1 +local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1 name = m.C(name) --- identifiers only have meaning in a given environment -local Identifier = name * m.Carg(1) +-- a defined name only have meaning in a given environment +local Def = name * m.Carg(1) + + +local function getdef (id, defs) + local c = defs and defs[id] + if not c then error("undefined name: " .. id) end + return c +end + +-- match a name and return a group of its corresponding definition +-- and 'f' (to be folded in 'Suffix') +local function defwithfunc (f) + return m.Cg(Def / getdef * m.Cc(f)) +end + local num = m.C(m.R"09"^1) * S / tonumber @@ -122,7 +132,7 @@ local String = "'" * m.C((any - "'")^0) * "'" + '"' * m.C((any - '"')^0) * '"' -local defined = "%" * Identifier / function (c,Defs) +local defined = "%" * Def / function (c,Defs) local cat = Defs and Defs[c] or Predef[c] if not cat then error ("name '" .. c .. "' undefined") end return cat @@ -130,16 +140,16 @@ end local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R -local item = defined + Range + m.C(any) +local item = (defined + Range + m.C(any)) / m.P local Class = "[" * (m.C(m.P"^"^-1)) -- optional complement symbol - * m.Cf(item * (item - "]")^0, mt.__add) / + * (item * ((item % mt.__add) - "]")^0) / function (c, p) return c == "^" and any - p or p end * "]" -local function adddef (t, k, Defs, exp) +local function adddef (t, k, exp) if t[k] then error("'"..k.."' already defined as a rule") else @@ -148,32 +158,41 @@ local function adddef (t, k, Defs, exp) return t end -local function firstdef (n, Defs, r) return adddef({n}, n, Defs, r) end +local function firstdef (n, r) return adddef({n}, n, r) end + +local function NT (n, b) + if not b then + error("rule '"..n.."' used outside a grammar") + else return mm.V(n) + end +end local exp = m.P{ "Exp", Exp = S * ( m.V"Grammar" - + m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) ); - Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul) - * (#exp_follow + patt_error); + + m.V"Seq" * ("/" * S * m.V"Seq" % mt.__add)^0 ); + Seq = (m.Cc(m.P"") * (m.V"Prefix" % mt.__mul)^0) + * (#seq_follow + patt_error); Prefix = "&" * S * m.V"Prefix" / mt.__len + "!" * S * m.V"Prefix" / mt.__unm + m.V"Suffix"; - Suffix = m.Cf(m.V"Primary" * S * + Suffix = m.V"Primary" * S * ( ( m.P"+" * m.Cc(1, mt.__pow) + m.P"*" * m.Cc(0, mt.__pow) + m.P"?" * m.Cc(-1, mt.__pow) + "^" * ( m.Cg(num * m.Cc(mult)) + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow)) ) - + "->" * S * ( m.Cg(String * m.Cc(mt.__div)) + + "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div)) + m.P"{}" * m.Cc(nil, m.Ct) - + m.Cg(Identifier / getdef * m.Cc(mt.__div)) + + defwithfunc(mt.__div) ) - + "=>" * S * m.Cg(Identifier / getdef * m.Cc(m.Cmt)) - ) * S - )^0, function (a,b,f) return f(a,b) end ); + + "=>" * S * defwithfunc(mm.Cmt) + + ">>" * S * defwithfunc(mt.__mod) + + "~>" * S * defwithfunc(mm.Cf) + ) % function (a,b,f) return f(a,b) end * S + )^0; Primary = "(" * m.V"Exp" * ")" + String / mm.P + Class @@ -183,16 +202,16 @@ local exp = m.P{ "Exp", + "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end + m.P"{}" / mm.Cp + "{~" * m.V"Exp" * "~}" / mm.Cs + + "{|" * m.V"Exp" * "|}" / mm.Ct + "{" * m.V"Exp" * "}" / mm.C + m.P"." * m.Cc(any) - + name * -arrow / mm.V - + "<" * name * ">" / mm.V; - Definition = Identifier * arrow * m.V"Exp"; - Grammar = m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0, adddef) / - mm.P + + (name * -arrow + "<" * name * ">") * m.Cb("G") / NT; + Definition = name * arrow * m.V"Exp"; + Grammar = m.Cg(m.Cc(true), "G") * + ((m.V"Definition" / firstdef) * (m.V"Definition" % adddef)^0) / mm.P } -local pattern = S * exp / mm.P * (-any + patt_error) +local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error) local function compile (p, defs) @@ -214,11 +233,14 @@ end local function find (s, p, i) local cp = fmem[p] if not cp then - cp = compile(p) - cp = mm.P{ mm.Cp() * cp + 1 * mm.V(1) } + cp = compile(p) / 0 + cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } fmem[p] = cp end - return cp:match(s, i or 1) + local i, e = cp:match(s, i or 1) + if i then return i, e - 1 + else return i + end end local function gsub (s, p, rep)