Initial checkin
This commit is contained in:
commit
23b6193024
5
conf.lua
Normal file
5
conf.lua
Normal file
|
@ -0,0 +1,5 @@
|
|||
function love.conf(t)
|
||||
t.window.width = 1280
|
||||
t.window.height = 1000
|
||||
t.window.resizable = true
|
||||
end
|
17
editor/editmode.fnl
Normal file
17
editor/editmode.fnl
Normal file
|
@ -0,0 +1,17 @@
|
|||
(local modes (require :editor.lovemode))
|
||||
(local core (require :core))
|
||||
|
||||
(local events [])
|
||||
(local editor-mode
|
||||
{:draw system.draw_love_frame
|
||||
:update (fn [...]
|
||||
(when (and core.active_view core.active_view.handle-love-update)
|
||||
(core.active_view:handle-love-update ...)))
|
||||
:handler (fn [...]
|
||||
(system.enqueue_love_event ...)
|
||||
(when (and core.active_view core.active_view.handle-love-event)
|
||||
(core.active_view:handle-love-event ...)))})
|
||||
|
||||
(modes:register :editor editor-mode)
|
||||
|
||||
{: editor-mode : events}
|
215
editor/imstate.fnl
Normal file
215
editor/imstate.fnl
Normal file
|
@ -0,0 +1,215 @@
|
|||
(local core (require :core))
|
||||
(local config (require :core.config))
|
||||
(local command (require :core.command))
|
||||
(local keymap (require :core.keymap))
|
||||
(local style (require :core.style))
|
||||
(local lume (require :lib.lume))
|
||||
|
||||
(fn attach-imstate [view]
|
||||
(set view.imstate {})
|
||||
(fn view.on_mouse_pressed [self button x y clicks]
|
||||
(tset self.imstate button :pressed)
|
||||
(self.__index.on_mouse_pressed self button x y clicks))
|
||||
(fn view.on_mouse_released [self button x y]
|
||||
(tset self.imstate button :released)
|
||||
(self.__index.on_mouse_released self button x y))
|
||||
(fn view.on_key_pressed [self key]
|
||||
(when (= self.imstate.keys nil)
|
||||
(set self.imstate.keys []))
|
||||
(table.insert self.imstate.keys key))
|
||||
(fn view.on_text_input [self text]
|
||||
(set self.imstate.text (.. (or self.imstate.text "") text))
|
||||
(self.__index.on_text_input self text))
|
||||
(fn view.draw [self]
|
||||
(set self.cursor nil)
|
||||
(self.__index.draw self)
|
||||
(when (= self.cursor nil) (set self.cursor :arrow))
|
||||
(set self.imstate.keys nil)
|
||||
(set self.imstate.text nil)
|
||||
(when (= self.imstate.left :released)
|
||||
(set self.imstate.active nil))
|
||||
(each [_ button (pairs [:left :middle :right])]
|
||||
(tset self.imstate button
|
||||
(match (. self.imstate button)
|
||||
:pressed :down
|
||||
:down :down
|
||||
:released nil)))))
|
||||
|
||||
(fn register-keys [keys]
|
||||
(local commands {})
|
||||
(local keymaps {})
|
||||
(each [_ key (ipairs keys)]
|
||||
(local command-name (.. "imstate:" key))
|
||||
(tset commands command-name #(core.active_view:on_key_pressed key))
|
||||
(tset keymaps key command-name))
|
||||
(command.add #(not= (-?> core.active_view.imstate (. :focus)) nil) commands)
|
||||
(keymap.add keymaps))
|
||||
|
||||
(register-keys [:backspace :delete :left :right :shift+left :shift+right :home :end :shift+home :shift+end
|
||||
:ctrl+left :ctrl+right :ctrl+shift+left :ctrl+shift+right :ctrl+c :ctrl+v])
|
||||
|
||||
(fn cmd-predicate [p]
|
||||
(var p-fn p)
|
||||
(when (= (type p-fn) :string) (set p-fn (require p-fn)))
|
||||
(when (= (type p-fn) :table)
|
||||
(local cls p-fn)
|
||||
(set p-fn (fn [] (core.active_view:is cls))))
|
||||
(fn [] (when (= (-?> core.active_view.imstate (. :focus)) nil)
|
||||
(p-fn))))
|
||||
|
||||
(fn make-tag [tag]
|
||||
(match (type tag)
|
||||
:string tag
|
||||
:table (table.concat tag "::")
|
||||
_ (tostring tag)))
|
||||
|
||||
(fn mouse-inside [x y w h]
|
||||
(local (mx my) (values (love.mouse.getX) (love.mouse.getY)))
|
||||
(and (>= mx x) (<= mx (+ x w)) (>= my y) (<= my (+ y h))))
|
||||
|
||||
(fn activate [view tag x y w h]
|
||||
(when (and (= view.imstate.left :pressed) (mouse-inside x y w h))
|
||||
(set view.imstate.active (make-tag tag))
|
||||
true))
|
||||
(fn active? [view tag] (= view.imstate.active (make-tag tag)))
|
||||
(fn button [view tag x y w h]
|
||||
(when (mouse-inside x y w h) (set view.cursor :hand))
|
||||
(activate view tag x y w h)
|
||||
(and (active? view tag) (= view.imstate.left :released) (mouse-inside x y w h)))
|
||||
|
||||
(fn textbutton [view label x y]
|
||||
(local (w h) (values (+ (style.font:get_width label) 8) 24))
|
||||
(renderer.draw_rect x y w h style.selection)
|
||||
(renderer.draw_text style.font label (+ x 4) (+ y 4) style.text)
|
||||
(values (button view label x y w h) (+ y h)))
|
||||
|
||||
(fn checkbox [view name isset x y ?tag]
|
||||
(love.graphics.rectangle (if isset :fill :line) x y 12 12)
|
||||
(local xEnd (renderer.draw_text style.font name (+ x 16) y style.text))
|
||||
(love.graphics.setColor 1 1 1 1)
|
||||
(button view (or ?tag name) x y (- xEnd x) 12))
|
||||
|
||||
(fn focused? [view tag] (= tag (-?> view.imstate.focus (. :tag))))
|
||||
(fn focus [view tag x y w h opts]
|
||||
(if (activate view tag x y w h)
|
||||
(set view.imstate.focus
|
||||
(doto (lume.clone (or opts {}))
|
||||
(tset :tag tag)))
|
||||
|
||||
(and (= view.imstate.left :released) (focused? view tag) (not (active? view tag)))
|
||||
(set view.imstate.focus nil))
|
||||
(focused? view tag))
|
||||
|
||||
(local blink_period 0.8)
|
||||
(fn x-from-i [s i xLeft font]
|
||||
(if (or (<= i 1) (= s "")) xLeft
|
||||
(x-from-i (s:sub 2) (- i 1) (+ xLeft (font:get_width (s:sub 1 1))) font)))
|
||||
(fn i-from-x [s x xLeft font ?i]
|
||||
(local i (or ?i 1))
|
||||
(local w (font:get_width (s:sub 1 1)))
|
||||
(local xMid (+ xLeft (/ w 2)))
|
||||
(if (or (<= x xMid) (= s "")) i
|
||||
(i-from-x (s:sub 2) x (+ xLeft w) font (+ i 1))))
|
||||
|
||||
(fn next-match [text i di pred]
|
||||
(local imax (+ (length text) 1))
|
||||
(local inext (+ i di))
|
||||
(if (<= inext 1) 1
|
||||
(> inext imax) imax
|
||||
(pred (text:sub inext inext)) (if (< di 0) i inext)
|
||||
(next-match text inext di pred)))
|
||||
(fn is-nonword-char [char] (config.non_word_chars:find char nil true))
|
||||
(fn next-word [text i di]
|
||||
(let [iwordboundary (next-match text i di #(is-nonword-char $1))]
|
||||
(next-match text iwordboundary di #(not (is-nonword-char $1)))))
|
||||
|
||||
(fn textnav [key i text]
|
||||
(local imax (+ (length text) 1))
|
||||
(match key
|
||||
:left (math.max 1 (- i 1))
|
||||
:right (math.min imax (+ i 1))
|
||||
:ctrl+left (next-word text i -1)
|
||||
:ctrl+right (next-word text i 1)
|
||||
:home 1
|
||||
:end imax))
|
||||
|
||||
(fn selection-span [view]
|
||||
(let [f view.imstate.focus
|
||||
iStart (math.min f.i f.iAnchor)
|
||||
iLim (math.max f.i f.iAnchor)]
|
||||
(values iStart iLim)))
|
||||
(fn selection-text [view text]
|
||||
(local (iStart iLim) (selection-span view))
|
||||
(text:sub iStart (- iLim 1)))
|
||||
|
||||
(fn replace-selection [view s replacement ?iStart ?iLim]
|
||||
(local (iStart iLim) (if ?iLim (values ?iStart ?iLim) (selection-span view)))
|
||||
(local text
|
||||
(.. (s:sub 1 (- iStart 1))
|
||||
replacement
|
||||
(s:sub iLim)))
|
||||
(local iNew (+ iStart (length replacement)))
|
||||
(set view.imstate.focus.i iNew)
|
||||
(set view.imstate.focus.iAnchor iNew)
|
||||
text)
|
||||
|
||||
(fn textbox [view tag text x y w]
|
||||
(var textNew (or text ""))
|
||||
(local (h hText xText yText) (values 20 16 (+ x 2) (+ y 2)))
|
||||
|
||||
; handle key events
|
||||
(when (focus view tag x y w h {:i 1 :iAnchor 1 :blink (love.timer.getTime)})
|
||||
(local f view.imstate.focus)
|
||||
(when view.imstate.text
|
||||
(set textNew (replace-selection view textNew view.imstate.text)))
|
||||
(each [_ key (ipairs (or view.imstate.keys []))]
|
||||
(set view.imstate.focus.blink (love.timer.getTime))
|
||||
(if (= key :ctrl+c) (system.set_clipboard (selection-text view textNew))
|
||||
(= key :ctrl+v) (set textNew (replace-selection view textNew (system.get_clipboard)))
|
||||
(key:find "shift%+") (set f.i (or (textnav (key:gsub "shift%+" "") f.i textNew) f.i))
|
||||
(let [iNav (textnav key f.i textNew)]
|
||||
(when iNav
|
||||
(set f.i iNav)
|
||||
(set f.iAnchor iNav))
|
||||
(when (or (= key :delete) (= key :backspace))
|
||||
(local (iStartDel iLimDel)
|
||||
(if (not= f.i f.iAnchor) (selection-span view)
|
||||
(= key :delete) (values f.i (+ f.i 1))
|
||||
(= key :backspace) (values (math.max 1 (- f.i 1)) f.i)))
|
||||
(set textNew (replace-selection view textNew "" iStartDel iLimDel)))))))
|
||||
|
||||
; handle mouse events
|
||||
(when (mouse-inside x y w h) (set view.cursor :ibeam))
|
||||
(when (and (focused? view tag) (active? view tag) (mouse-inside x y w h))
|
||||
(local mouse-i (i-from-x textNew (love.mouse.getX) x style.font))
|
||||
(when (= view.imstate.left :pressed)
|
||||
(set view.imstate.focus.iAnchor mouse-i))
|
||||
(set view.imstate.focus.i mouse-i))
|
||||
|
||||
; draw box
|
||||
(love.graphics.setLineWidth 1)
|
||||
(love.graphics.rectangle :line x y w h)
|
||||
(if (focused? view tag)
|
||||
; draw text with selection + caret
|
||||
(let [(iStart iLim) (selection-span view)
|
||||
xSelect (renderer.draw_text style.font (textNew:sub 1 (- iStart 1)) xText yText style.text)
|
||||
sSelect (textNew:sub iStart (- iLim 1))
|
||||
wSelect (style.font:get_width sSelect)
|
||||
xTail (+ xSelect wSelect)]
|
||||
(when (> wSelect 0)
|
||||
(renderer.draw_rect xSelect yText wSelect hText style.selection)
|
||||
(renderer.draw_text style.font sSelect xSelect yText style.text))
|
||||
(renderer.draw_text style.font (textNew:sub iLim) xTail yText style.text)
|
||||
(when (or (active? view tag)
|
||||
(< (% (- (love.timer.getTime) view.imstate.focus.blink) (* blink_period 2)) blink_period))
|
||||
(renderer.draw_rect (x-from-i textNew view.imstate.focus.i xText style.font) yText style.caret_width hText style.caret)))
|
||||
; just draw the text
|
||||
(renderer.draw_text style.font textNew xText yText style.text))
|
||||
(love.graphics.setColor 1 1 1)
|
||||
(values textNew (+ y h)))
|
||||
|
||||
(fn textfield [view label text x y wLabel wText]
|
||||
(renderer.draw_text style.font label x y style.text)
|
||||
(textbox view label text (+ x wLabel) y wText))
|
||||
|
||||
{: attach-imstate : cmd-predicate : mouse-inside : activate : active? : button : checkbox : textbox : textfield : textbutton}
|
17
editor/init.fnl
Normal file
17
editor/init.fnl
Normal file
|
@ -0,0 +1,17 @@
|
|||
(local util (require :lib.util))
|
||||
(local core (require :core))
|
||||
(local command (require :core.command))
|
||||
(local keymap (require :core.keymap))
|
||||
(local common (require :core.common))
|
||||
|
||||
(fn inline-eval [eval]
|
||||
(let [ldoc core.active_view.doc
|
||||
(aline acol bline bcol) (ldoc:get_selection)
|
||||
inject #(ldoc:insert bline bcol (eval $1))]
|
||||
(if (and (= aline bline) (= acol bcol))
|
||||
(inject (ldoc:get_text aline 1 aline 10000000))
|
||||
(inject (ldoc:get_text aline acol bline bcol)))))
|
||||
|
||||
(require :editor.editmode)
|
||||
|
||||
{: inline-eval}
|
44
editor/lovemode.fnl
Normal file
44
editor/lovemode.fnl
Normal file
|
@ -0,0 +1,44 @@
|
|||
(local lume (require :lib.lume))
|
||||
(local common (require :core.common))
|
||||
(local style (require :core.style))
|
||||
|
||||
(local std-handlers love.handlers)
|
||||
(local modes {:name-to-mode {}
|
||||
:mode-index 1
|
||||
:names []
|
||||
: std-handlers})
|
||||
|
||||
(fn modes.cycle [self]
|
||||
(set self.mode-index (+ self.mode-index 1))
|
||||
(when (> self.mode-index (length self.names))
|
||||
(set self.mode-index 1))
|
||||
(self:switch (self:current)))
|
||||
|
||||
(fn modes.current [self]
|
||||
(. self.name-to-mode (. self.names self.mode-index)))
|
||||
|
||||
(fn add-mode-cycler [handlers]
|
||||
(fn mode-cycler [ev key ...]
|
||||
(when (and (= ev :keyreleased) (= key :f1))
|
||||
(modes:cycle))
|
||||
(when (rawget handlers :any) (handlers.any ev key ...)))
|
||||
(setmetatable {:any mode-cycler} {:__index handlers}))
|
||||
|
||||
(fn modes.switch [self mode]
|
||||
(set love.handlers (add-mode-cycler (if mode.handler {:any mode.handler} std-handlers)))
|
||||
(set love.update mode.update)
|
||||
(set love.draw (fn []
|
||||
(xpcall mode.draw (fn [msg]
|
||||
(love.graphics.reset)
|
||||
(love.graphics.setColor 1 0 0 1)
|
||||
(love.graphics.setNewFont 14)
|
||||
(love.graphics.printf (.. msg "\n" (debug.traceback))
|
||||
20 20 (- (love.graphics.getWidth) 40)))))))
|
||||
|
||||
(fn modes.register [self name mode]
|
||||
(tset self.name-to-mode name mode)
|
||||
(when (not (lume.any self.names #(= $1 name)))
|
||||
(table.insert self.names name))
|
||||
(when (= (length self.names) 1) (self:cycle)))
|
||||
|
||||
modes
|
25
editor/modeview.fnl
Normal file
25
editor/modeview.fnl
Normal file
|
@ -0,0 +1,25 @@
|
|||
(local modes (require :editor.lovemode))
|
||||
(local View (require :core.view))
|
||||
|
||||
(local ModeView (View:extend))
|
||||
|
||||
(fn ModeView.new [self mode]
|
||||
(ModeView.super.new self)
|
||||
(set self.mode mode))
|
||||
|
||||
(fn ModeView.draw [self]
|
||||
(love.graphics.push :all)
|
||||
(love.graphics.translate self.position.x self.position.y)
|
||||
(xpcall self.mode.draw (fn [...] (love.graphics.pop) (error ...)))
|
||||
(love.graphics.pop))
|
||||
|
||||
(fn ModeView.handle-love-update [self ...]
|
||||
(when self.mode.update (self.mode.update ...)))
|
||||
|
||||
(fn ModeView.handle-love-event [self ev ...]
|
||||
(if self.mode.handler
|
||||
(self.mode.handler ev ...)
|
||||
((. modes.std-handlers ev) ...)))
|
||||
|
||||
ModeView
|
||||
|
12
game/entities/bomberman.fnl
Normal file
12
game/entities/bomberman.fnl
Normal file
|
@ -0,0 +1,12 @@
|
|||
(local Entity (require :game.entity))
|
||||
(local rules (require :game.rules))
|
||||
|
||||
(local Bomberman (Entity:extend))
|
||||
(set Bomberman.keymap {:up :w :down :s :left :a :right :d :bomb :x})
|
||||
(set Bomberman.color [0.2 0.2 0.2])
|
||||
(fn Bomberman.update [self dt]
|
||||
(Bomberman.super.update self dt)
|
||||
(when (love.keyboard.isDown self.keymap.bomb)
|
||||
(rules.place-bomb self.x self.y)))
|
||||
|
||||
Bomberman
|
9
game/entities/pacman.fnl
Normal file
9
game/entities/pacman.fnl
Normal file
|
@ -0,0 +1,9 @@
|
|||
(local Entity (require :game.entity))
|
||||
(local rules (require :game.rules))
|
||||
|
||||
(local Pacman (Entity:extend))
|
||||
(set Pacman.keymap {:up :up :down :down :left :left :right :right})
|
||||
(set Pacman.color [1 1 0])
|
||||
|
||||
Pacman
|
||||
|
24
game/entity.fnl
Normal file
24
game/entity.fnl
Normal file
|
@ -0,0 +1,24 @@
|
|||
(local util (require :lib.util))
|
||||
(local {: vec* : dir-from-key} (util.require :game.helpers))
|
||||
(local rules (require :game.rules))
|
||||
(local Object (require :core.object))
|
||||
|
||||
(local Entity (Object:extend))
|
||||
(fn Entity.new [self x y]
|
||||
(set self.x x)
|
||||
(set self.y y)
|
||||
(set self.velocity [0 0]))
|
||||
(fn Entity.draw [self]
|
||||
(when (= self.color nil)
|
||||
(set self.color [(math.random) (math.random) (math.random)]))
|
||||
(love.graphics.setColor (table.unpack self.color))
|
||||
(love.graphics.circle :fill self.x self.y 16))
|
||||
(fn Entity.move [self [dx dy]]
|
||||
(set self.velocity [dx dy])
|
||||
(set self.x (rules.collide :x self dx))
|
||||
(set self.y (rules.collide :y self dy)))
|
||||
(fn Entity.update [self dt]
|
||||
(self:move (vec* (dir-from-key self.keymap) (* self.speed dt))))
|
||||
(set Entity.speed 64)
|
||||
|
||||
Entity
|
12
game/helpers.fnl
Normal file
12
game/helpers.fnl
Normal file
|
@ -0,0 +1,12 @@
|
|||
(fn dir-from-key [keymap]
|
||||
(var (dx dy) (values 0 0))
|
||||
(when (love.keyboard.isDown keymap.up) (set dy -1))
|
||||
(when (love.keyboard.isDown keymap.down) (set dy 1))
|
||||
(when (love.keyboard.isDown keymap.left) (set dx -1))
|
||||
(when (love.keyboard.isDown keymap.right) (set dx 1))
|
||||
[dx dy])
|
||||
|
||||
(fn vec* [vec scalar]
|
||||
(icollect [_ v (ipairs vec)] (* v scalar)))
|
||||
|
||||
{: dir-from-key : vec*}
|
21
game/init.fnl
Normal file
21
game/init.fnl
Normal file
|
@ -0,0 +1,21 @@
|
|||
(local util (require :lib.util))
|
||||
(local modes (require :editor.lovemode))
|
||||
(local ModeView (require :editor.modeview))
|
||||
(local state (require :game.state))
|
||||
(local core (require :core))
|
||||
(local command (require :core.command))
|
||||
|
||||
(local gamemode (require :game.mode))
|
||||
(modes:register :game gamemode)
|
||||
|
||||
(set state.entities
|
||||
[((require :game.entities.bomberman) 20 20)
|
||||
((require :game.entities.pacman) 350 350)])
|
||||
|
||||
(command.add nil {
|
||||
"love:game" (fn []
|
||||
(let [node (core.root_view:get_active_node)]
|
||||
(node:add_view (ModeView gamemode))))
|
||||
})
|
||||
|
||||
{}
|
12
game/mode.fnl
Normal file
12
game/mode.fnl
Normal file
|
@ -0,0 +1,12 @@
|
|||
(local state (require :game.state))
|
||||
|
||||
(fn update [dt]
|
||||
(each [_ entity (ipairs state.entities)]
|
||||
(entity:update dt)))
|
||||
|
||||
(fn draw []
|
||||
(each [_ entity (ipairs state.entities)]
|
||||
(entity:draw)))
|
||||
|
||||
{: update : draw}
|
||||
|
8
game/rules.fnl
Normal file
8
game/rules.fnl
Normal file
|
@ -0,0 +1,8 @@
|
|||
(local state (require :game.state))
|
||||
|
||||
(fn collide [axis entity d]
|
||||
(+ (. entity axis) d))
|
||||
|
||||
(fn place-bomb [x y])
|
||||
{: collide : place-bomb}
|
||||
|
1
game/state.fnl
Normal file
1
game/state.fnl
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
16
game/tiles.fnl
Normal file
16
game/tiles.fnl
Normal file
|
@ -0,0 +1,16 @@
|
|||
(local tiles
|
||||
[{:name :empty
|
||||
:draw (fn [])}
|
||||
{:name :strongwall
|
||||
:draw (fn [x y w h]
|
||||
(love.graphics.setColor 0.4 0.4 0.4)
|
||||
(love.graphics.rectangle :fill x y w h))}
|
||||
{:name :weakwall
|
||||
:draw (fn [x y w h]
|
||||
(love.graphics.setColor 0.4 0.4 0.4)
|
||||
(love.graphics.rectangle :fill x y w h))}
|
||||
{:name :dot
|
||||
:draw (fn [x y w h]
|
||||
(love.graphics.setColor 1 1 1)
|
||||
(love.graphics.circle :fill x y w 5))}
|
||||
])
|
89
lib/bencode.lua
Normal file
89
lib/bencode.lua
Normal file
|
@ -0,0 +1,89 @@
|
|||
-- Based on bencode.lua from the jeejah project by Phil Hagelberg
|
||||
-- Distributed under the MIT license
|
||||
-- https://gitlab.com/technomancy/jeejah/
|
||||
|
||||
local encode, decode
|
||||
|
||||
local function decode_list(str, t, total_len)
|
||||
-- print("list", str, lume.serialize(t))
|
||||
if #str == 0 then error("Incomplete") end
|
||||
if(str:sub(1,1) == "e") then return t, total_len + 1 end
|
||||
local value, v_len = decode(str)
|
||||
table.insert(t, value)
|
||||
total_len = total_len + v_len
|
||||
return decode_list(str:sub(v_len + 1), t, total_len)
|
||||
end
|
||||
|
||||
local function decode_table(str, t, total_len)
|
||||
-- print("table", str, lume.serialize(t))
|
||||
if #str == 0 then error("Incomplete") end
|
||||
if(str:sub(1,1) == "e") then return t, total_len + 1 end
|
||||
local key, k_len = decode(str)
|
||||
local value, v_len = decode(str:sub(k_len+1))
|
||||
local end_pos = 1 + k_len + v_len
|
||||
t[key] = value
|
||||
total_len = total_len + k_len + v_len
|
||||
return decode_table(str:sub(end_pos), t, total_len)
|
||||
end
|
||||
|
||||
function decode(str)
|
||||
-- print("decoding", str)
|
||||
if #str == 0 then
|
||||
error("Incomplete")
|
||||
elseif(str:sub(1,1) == "l") then
|
||||
return decode_list(str:sub(2), {}, 1)
|
||||
elseif(str:sub(1,1) == "d") then
|
||||
return decode_table(str:sub(2), {}, 1)
|
||||
elseif(str:sub(1,1) == "i") then
|
||||
local iend = str:find("e")
|
||||
if iend == nil then error("Incomplete") end
|
||||
return(tonumber(str:sub(2, iend - 1))), iend
|
||||
elseif(str:match("[0-9]+:")) then
|
||||
local num_str = str:match("[0-9]+")
|
||||
local beginning_of_string = #num_str + 2
|
||||
local str_len = tonumber(num_str)
|
||||
local total_len = beginning_of_string + str_len - 1
|
||||
if #str < total_len then error("Incomplete") end
|
||||
return str:sub(beginning_of_string, total_len), total_len
|
||||
else
|
||||
error("Could not parse "..str)
|
||||
end
|
||||
end
|
||||
|
||||
local function encode_str(s) return #s .. ":" .. s end
|
||||
local function encode_int(n) return "i" .. tostring(n) .. "e" end
|
||||
|
||||
local function encode_table(t)
|
||||
local s = "d"
|
||||
for k,v in pairs(t) do s = s .. encode(k) .. encode(v) end
|
||||
return s .. "e"
|
||||
end
|
||||
|
||||
local function encode_list(l)
|
||||
local s = "l"
|
||||
for _,x in ipairs(l) do s = s .. encode(x) end
|
||||
return s .. "e"
|
||||
end
|
||||
|
||||
local function count(tbl)
|
||||
local i = 0
|
||||
for _ in pairs(tbl) do i = i + 1 end
|
||||
return i
|
||||
end
|
||||
|
||||
function encode(x)
|
||||
local unpack = unpack or table.unpack
|
||||
if(type(x) == "table" and select("#", unpack(x)) == count(x)) then
|
||||
return encode_list(x)
|
||||
elseif(type(x) == "table") then
|
||||
return encode_table(x)
|
||||
elseif(type(x) == "number" and math.floor(x) == x) then
|
||||
return encode_int(x)
|
||||
elseif(type(x) == "string") then
|
||||
return encode_str(x)
|
||||
else
|
||||
error("Could not encode " .. type(x) .. ": " .. tostring(x))
|
||||
end
|
||||
end
|
||||
|
||||
return {decode=decode, encode=encode}
|
714
lib/dkjson.lua
Normal file
714
lib/dkjson.lua
Normal file
|
@ -0,0 +1,714 @@
|
|||
-- Module options:
|
||||
local always_try_using_lpeg = true
|
||||
local register_global_module_table = false
|
||||
local global_module_name = 'json'
|
||||
|
||||
--[==[
|
||||
|
||||
David Kolf's JSON module for Lua 5.1/5.2
|
||||
|
||||
Version 2.5
|
||||
|
||||
|
||||
For the documentation see the corresponding readme.txt or visit
|
||||
<http://dkolf.de/src/dkjson-lua.fsl/>.
|
||||
|
||||
You can contact the author by sending an e-mail to 'david' at the
|
||||
domain 'dkolf.de'.
|
||||
|
||||
|
||||
Copyright (C) 2010-2013 David Heiko Kolf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
--]==]
|
||||
|
||||
-- global dependencies:
|
||||
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
|
||||
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
|
||||
local error, require, pcall, select = error, require, pcall, select
|
||||
local floor, huge = math.floor, math.huge
|
||||
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
|
||||
string.rep, string.gsub, string.sub, string.byte, string.char,
|
||||
string.find, string.len, string.format
|
||||
local strmatch = string.match
|
||||
local concat = table.concat
|
||||
|
||||
local json = { version = "dkjson 2.5" }
|
||||
|
||||
if register_global_module_table then
|
||||
_G[global_module_name] = json
|
||||
end
|
||||
|
||||
local _ENV = nil -- blocking globals in Lua 5.2
|
||||
|
||||
pcall (function()
|
||||
-- Enable access to blocked metatables.
|
||||
-- Don't worry, this module doesn't change anything in them.
|
||||
local debmeta = require "debug".getmetatable
|
||||
if debmeta then getmetatable = debmeta end
|
||||
end)
|
||||
|
||||
json.null = setmetatable ({}, {
|
||||
__tojson = function () return "null" end
|
||||
})
|
||||
|
||||
local function isarray (tbl)
|
||||
local max, n, arraylen = 0, 0, 0
|
||||
for k,v in pairs (tbl) do
|
||||
if k == 'n' and type(v) == 'number' then
|
||||
arraylen = v
|
||||
if v > max then
|
||||
max = v
|
||||
end
|
||||
else
|
||||
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
|
||||
return false
|
||||
end
|
||||
if k > max then
|
||||
max = k
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
if max > 10 and max > arraylen and max > n * 2 then
|
||||
return false -- don't create an array with too many holes
|
||||
end
|
||||
return true, max
|
||||
end
|
||||
|
||||
local escapecodes = {
|
||||
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
|
||||
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
|
||||
}
|
||||
|
||||
local function escapeutf8 (uchar)
|
||||
local value = escapecodes[uchar]
|
||||
if value then
|
||||
return value
|
||||
end
|
||||
local a, b, c, d = strbyte (uchar, 1, 4)
|
||||
a, b, c, d = a or 0, b or 0, c or 0, d or 0
|
||||
if a <= 0x7f then
|
||||
value = a
|
||||
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
|
||||
value = (a - 0xc0) * 0x40 + b - 0x80
|
||||
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
|
||||
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
|
||||
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
|
||||
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
|
||||
else
|
||||
return ""
|
||||
end
|
||||
if value <= 0xffff then
|
||||
return strformat ("\\u%.4x", value)
|
||||
elseif value <= 0x10ffff then
|
||||
-- encode as UTF-16 surrogate pair
|
||||
value = value - 0x10000
|
||||
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
|
||||
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
local function fsub (str, pattern, repl)
|
||||
-- gsub always builds a new string in a buffer, even when no match
|
||||
-- exists. First using find should be more efficient when most strings
|
||||
-- don't contain the pattern.
|
||||
if strfind (str, pattern) then
|
||||
return gsub (str, pattern, repl)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
local function quotestring (value)
|
||||
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
|
||||
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
|
||||
if strfind (value, "[\194\216\220\225\226\239]") then
|
||||
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
|
||||
value = fsub (value, "\216[\128-\132]", escapeutf8)
|
||||
value = fsub (value, "\220\143", escapeutf8)
|
||||
value = fsub (value, "\225\158[\180\181]", escapeutf8)
|
||||
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
|
||||
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
|
||||
value = fsub (value, "\239\187\191", escapeutf8)
|
||||
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
|
||||
end
|
||||
return "\"" .. value .. "\""
|
||||
end
|
||||
json.quotestring = quotestring
|
||||
|
||||
local function replace(str, o, n)
|
||||
local i, j = strfind (str, o, 1, true)
|
||||
if i then
|
||||
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
-- locale independent num2str and str2num functions
|
||||
local decpoint, numfilter
|
||||
|
||||
local function updatedecpoint ()
|
||||
decpoint = strmatch(tostring(0.5), "([^05+])")
|
||||
-- build a filter that can be used to remove group separators
|
||||
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
|
||||
end
|
||||
|
||||
updatedecpoint()
|
||||
|
||||
local function num2str (num)
|
||||
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
|
||||
end
|
||||
|
||||
local function str2num (str)
|
||||
local num = tonumber(replace(str, ".", decpoint))
|
||||
if not num then
|
||||
updatedecpoint()
|
||||
num = tonumber(replace(str, ".", decpoint))
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
local function addnewline2 (level, buffer, buflen)
|
||||
buffer[buflen+1] = "\n"
|
||||
buffer[buflen+2] = strrep (" ", level)
|
||||
buflen = buflen + 2
|
||||
return buflen
|
||||
end
|
||||
|
||||
function json.addnewline (state)
|
||||
if state.indent then
|
||||
state.bufferlen = addnewline2 (state.level or 0,
|
||||
state.buffer, state.bufferlen or #(state.buffer))
|
||||
end
|
||||
end
|
||||
|
||||
local encode2 -- forward declaration
|
||||
|
||||
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
local kt = type (key)
|
||||
if kt ~= 'string' and kt ~= 'number' then
|
||||
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
|
||||
end
|
||||
if prev then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ","
|
||||
end
|
||||
if indent then
|
||||
buflen = addnewline2 (level, buffer, buflen)
|
||||
end
|
||||
buffer[buflen+1] = quotestring (key)
|
||||
buffer[buflen+2] = ":"
|
||||
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
|
||||
end
|
||||
|
||||
local function appendcustom(res, buffer, state)
|
||||
local buflen = state.bufferlen
|
||||
if type (res) == 'string' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = res
|
||||
end
|
||||
return buflen
|
||||
end
|
||||
|
||||
local function exception(reason, value, state, buffer, buflen, defaultmessage)
|
||||
defaultmessage = defaultmessage or reason
|
||||
local handler = state.exception
|
||||
if not handler then
|
||||
return nil, defaultmessage
|
||||
else
|
||||
state.bufferlen = buflen
|
||||
local ret, msg = handler (reason, value, state, defaultmessage)
|
||||
if not ret then return nil, msg or defaultmessage end
|
||||
return appendcustom(ret, buffer, state)
|
||||
end
|
||||
end
|
||||
|
||||
function json.encodeexception(reason, value, state, defaultmessage)
|
||||
return quotestring("<" .. defaultmessage .. ">")
|
||||
end
|
||||
|
||||
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
local valtype = type (value)
|
||||
local valmeta = getmetatable (value)
|
||||
valmeta = type (valmeta) == 'table' and valmeta -- only tables
|
||||
local valtojson = valmeta and valmeta.__tojson
|
||||
if valtojson then
|
||||
if tables[value] then
|
||||
return exception('reference cycle', value, state, buffer, buflen)
|
||||
end
|
||||
tables[value] = true
|
||||
state.bufferlen = buflen
|
||||
local ret, msg = valtojson (value, state)
|
||||
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
|
||||
tables[value] = nil
|
||||
buflen = appendcustom(ret, buffer, state)
|
||||
elseif value == nil then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "null"
|
||||
elseif valtype == 'number' then
|
||||
local s
|
||||
if value ~= value or value >= huge or -value >= huge then
|
||||
-- This is the behaviour of the original JSON implementation.
|
||||
s = "null"
|
||||
else
|
||||
s = num2str (value)
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = s
|
||||
elseif valtype == 'boolean' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = value and "true" or "false"
|
||||
elseif valtype == 'string' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = quotestring (value)
|
||||
elseif valtype == 'table' then
|
||||
if tables[value] then
|
||||
return exception('reference cycle', value, state, buffer, buflen)
|
||||
end
|
||||
tables[value] = true
|
||||
level = level + 1
|
||||
local isa, n = isarray (value)
|
||||
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
|
||||
isa = false
|
||||
end
|
||||
local msg
|
||||
if isa then -- JSON array
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "["
|
||||
for i = 1, n do
|
||||
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
if i < n then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ","
|
||||
end
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "]"
|
||||
else -- JSON object
|
||||
local prev = false
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "{"
|
||||
local order = valmeta and valmeta.__jsonorder or globalorder
|
||||
if order then
|
||||
local used = {}
|
||||
n = #order
|
||||
for i = 1, n do
|
||||
local k = order[i]
|
||||
local v = value[k]
|
||||
if v then
|
||||
used[k] = true
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
for k,v in pairs (value) do
|
||||
if not used[k] then
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
else -- unordered
|
||||
for k,v in pairs (value) do
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
if indent then
|
||||
buflen = addnewline2 (level - 1, buffer, buflen)
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "}"
|
||||
end
|
||||
tables[value] = nil
|
||||
else
|
||||
return exception ('unsupported type', value, state, buffer, buflen,
|
||||
"type '" .. valtype .. "' is not supported by JSON.")
|
||||
end
|
||||
return buflen
|
||||
end
|
||||
|
||||
function json.encode (value, state)
|
||||
state = state or {}
|
||||
local oldbuffer = state.buffer
|
||||
local buffer = oldbuffer or {}
|
||||
state.buffer = buffer
|
||||
updatedecpoint()
|
||||
local ret, msg = encode2 (value, state.indent, state.level or 0,
|
||||
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
|
||||
if not ret then
|
||||
error (msg, 2)
|
||||
elseif oldbuffer == buffer then
|
||||
state.bufferlen = ret
|
||||
return true
|
||||
else
|
||||
state.bufferlen = nil
|
||||
state.buffer = nil
|
||||
return concat (buffer)
|
||||
end
|
||||
end
|
||||
|
||||
local function loc (str, where)
|
||||
local line, pos, linepos = 1, 1, 0
|
||||
while true do
|
||||
pos = strfind (str, "\n", pos, true)
|
||||
if pos and pos < where then
|
||||
line = line + 1
|
||||
linepos = pos
|
||||
pos = pos + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return "line " .. line .. ", column " .. (where - linepos)
|
||||
end
|
||||
|
||||
local function unterminated (str, what, where)
|
||||
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
|
||||
end
|
||||
|
||||
local function scanwhite (str, pos)
|
||||
while true do
|
||||
pos = strfind (str, "%S", pos)
|
||||
if not pos then return nil end
|
||||
local sub2 = strsub (str, pos, pos + 1)
|
||||
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
|
||||
-- UTF-8 Byte Order Mark
|
||||
pos = pos + 3
|
||||
elseif sub2 == "//" then
|
||||
pos = strfind (str, "[\n\r]", pos + 2)
|
||||
if not pos then return nil end
|
||||
elseif sub2 == "/*" then
|
||||
pos = strfind (str, "*/", pos + 2)
|
||||
if not pos then return nil end
|
||||
pos = pos + 2
|
||||
else
|
||||
return pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local escapechars = {
|
||||
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
|
||||
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
|
||||
}
|
||||
|
||||
local function unichar (value)
|
||||
if value < 0 then
|
||||
return nil
|
||||
elseif value <= 0x007f then
|
||||
return strchar (value)
|
||||
elseif value <= 0x07ff then
|
||||
return strchar (0xc0 + floor(value/0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
elseif value <= 0xffff then
|
||||
return strchar (0xe0 + floor(value/0x1000),
|
||||
0x80 + (floor(value/0x40) % 0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
elseif value <= 0x10ffff then
|
||||
return strchar (0xf0 + floor(value/0x40000),
|
||||
0x80 + (floor(value/0x1000) % 0x40),
|
||||
0x80 + (floor(value/0x40) % 0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function scanstring (str, pos)
|
||||
local lastpos = pos + 1
|
||||
local buffer, n = {}, 0
|
||||
while true do
|
||||
local nextpos = strfind (str, "[\"\\]", lastpos)
|
||||
if not nextpos then
|
||||
return unterminated (str, "string", pos)
|
||||
end
|
||||
if nextpos > lastpos then
|
||||
n = n + 1
|
||||
buffer[n] = strsub (str, lastpos, nextpos - 1)
|
||||
end
|
||||
if strsub (str, nextpos, nextpos) == "\"" then
|
||||
lastpos = nextpos + 1
|
||||
break
|
||||
else
|
||||
local escchar = strsub (str, nextpos + 1, nextpos + 1)
|
||||
local value
|
||||
if escchar == "u" then
|
||||
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
|
||||
if value then
|
||||
local value2
|
||||
if 0xD800 <= value and value <= 0xDBff then
|
||||
-- we have the high surrogate of UTF-16. Check if there is a
|
||||
-- low surrogate escaped nearby to combine them.
|
||||
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
|
||||
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
|
||||
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
|
||||
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
|
||||
else
|
||||
value2 = nil -- in case it was out of range for a low surrogate
|
||||
end
|
||||
end
|
||||
end
|
||||
value = value and unichar (value)
|
||||
if value then
|
||||
if value2 then
|
||||
lastpos = nextpos + 12
|
||||
else
|
||||
lastpos = nextpos + 6
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not value then
|
||||
value = escapechars[escchar] or escchar
|
||||
lastpos = nextpos + 2
|
||||
end
|
||||
n = n + 1
|
||||
buffer[n] = value
|
||||
end
|
||||
end
|
||||
if n == 1 then
|
||||
return buffer[1], lastpos
|
||||
elseif n > 1 then
|
||||
return concat (buffer), lastpos
|
||||
else
|
||||
return "", lastpos
|
||||
end
|
||||
end
|
||||
|
||||
local scanvalue -- forward declaration
|
||||
|
||||
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
|
||||
local len = strlen (str)
|
||||
local tbl, n = {}, 0
|
||||
local pos = startpos + 1
|
||||
if what == 'object' then
|
||||
setmetatable (tbl, objectmeta)
|
||||
else
|
||||
setmetatable (tbl, arraymeta)
|
||||
end
|
||||
while true do
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
local char = strsub (str, pos, pos)
|
||||
if char == closechar then
|
||||
return tbl, pos + 1
|
||||
end
|
||||
local val1, err
|
||||
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
if err then return nil, pos, err end
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
char = strsub (str, pos, pos)
|
||||
if char == ":" then
|
||||
if val1 == nil then
|
||||
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
|
||||
end
|
||||
pos = scanwhite (str, pos + 1)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
local val2
|
||||
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
if err then return nil, pos, err end
|
||||
tbl[val1] = val2
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
char = strsub (str, pos, pos)
|
||||
else
|
||||
n = n + 1
|
||||
tbl[n] = val1
|
||||
end
|
||||
if char == "," then
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
|
||||
pos = pos or 1
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then
|
||||
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
|
||||
end
|
||||
local char = strsub (str, pos, pos)
|
||||
if char == "{" then
|
||||
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
|
||||
elseif char == "[" then
|
||||
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
|
||||
elseif char == "\"" then
|
||||
return scanstring (str, pos)
|
||||
else
|
||||
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
|
||||
if pstart then
|
||||
local number = str2num (strsub (str, pstart, pend))
|
||||
if number then
|
||||
return number, pend + 1
|
||||
end
|
||||
end
|
||||
pstart, pend = strfind (str, "^%a%w*", pos)
|
||||
if pstart then
|
||||
local name = strsub (str, pstart, pend)
|
||||
if name == "true" then
|
||||
return true, pend + 1
|
||||
elseif name == "false" then
|
||||
return false, pend + 1
|
||||
elseif name == "null" then
|
||||
return nullval, pend + 1
|
||||
end
|
||||
end
|
||||
return nil, pos, "no valid JSON value at " .. loc (str, pos)
|
||||
end
|
||||
end
|
||||
|
||||
local function optionalmetatables(...)
|
||||
if select("#", ...) > 0 then
|
||||
return ...
|
||||
else
|
||||
return {__jsontype = 'object'}, {__jsontype = 'array'}
|
||||
end
|
||||
end
|
||||
|
||||
function json.decode (str, pos, nullval, ...)
|
||||
local objectmeta, arraymeta = optionalmetatables(...)
|
||||
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
end
|
||||
|
||||
function json.use_lpeg ()
|
||||
local g = require ("lpeg")
|
||||
|
||||
if g.version() == "0.11" then
|
||||
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
|
||||
end
|
||||
|
||||
local pegmatch = g.match
|
||||
local P, S, R = g.P, g.S, g.R
|
||||
|
||||
local function ErrorCall (str, pos, msg, state)
|
||||
if not state.msg then
|
||||
state.msg = msg .. " at " .. loc (str, pos)
|
||||
state.pos = pos
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function Err (msg)
|
||||
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
|
||||
end
|
||||
|
||||
local SingleLineComment = P"//" * (1 - S"\n\r")^0
|
||||
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
|
||||
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
|
||||
|
||||
local PlainChar = 1 - S"\"\\\n\r"
|
||||
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
|
||||
local HexDigit = R("09", "af", "AF")
|
||||
local function UTF16Surrogate (match, pos, high, low)
|
||||
high, low = tonumber (high, 16), tonumber (low, 16)
|
||||
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
|
||||
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
local function UTF16BMP (hex)
|
||||
return unichar (tonumber (hex, 16))
|
||||
end
|
||||
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
|
||||
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
|
||||
local Char = UnicodeEscape + EscapeSequence + PlainChar
|
||||
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
|
||||
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
|
||||
local Fractal = P"." * R"09"^0
|
||||
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
|
||||
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
|
||||
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
|
||||
local SimpleValue = Number + String + Constant
|
||||
local ArrayContent, ObjectContent
|
||||
|
||||
-- The functions parsearray and parseobject parse only a single value/pair
|
||||
-- at a time and store them directly to avoid hitting the LPeg limits.
|
||||
local function parsearray (str, pos, nullval, state)
|
||||
local obj, cont
|
||||
local npos
|
||||
local t, nt = {}, 0
|
||||
repeat
|
||||
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
|
||||
if not npos then break end
|
||||
pos = npos
|
||||
nt = nt + 1
|
||||
t[nt] = obj
|
||||
until cont == 'last'
|
||||
return pos, setmetatable (t, state.arraymeta)
|
||||
end
|
||||
|
||||
local function parseobject (str, pos, nullval, state)
|
||||
local obj, key, cont
|
||||
local npos
|
||||
local t = {}
|
||||
repeat
|
||||
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
|
||||
if not npos then break end
|
||||
pos = npos
|
||||
t[key] = obj
|
||||
until cont == 'last'
|
||||
return pos, setmetatable (t, state.objectmeta)
|
||||
end
|
||||
|
||||
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
|
||||
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
|
||||
local Value = Space * (Array + Object + SimpleValue)
|
||||
local ExpectedValue = Value + Space * Err "value expected"
|
||||
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
|
||||
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
|
||||
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
|
||||
local DecodeValue = ExpectedValue * g.Cp ()
|
||||
|
||||
function json.decode (str, pos, nullval, ...)
|
||||
local state = {}
|
||||
state.objectmeta, state.arraymeta = optionalmetatables(...)
|
||||
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
|
||||
if state.msg then
|
||||
return nil, state.pos, state.msg
|
||||
else
|
||||
return obj, retpos
|
||||
end
|
||||
end
|
||||
|
||||
-- use this function only once:
|
||||
json.use_lpeg = function () return json end
|
||||
|
||||
json.using_lpeg = true
|
||||
|
||||
return json -- so you can get the module using json = require "dkjson".use_lpeg()
|
||||
end
|
||||
|
||||
if always_try_using_lpeg then
|
||||
pcall (json.use_lpeg)
|
||||
end
|
||||
|
||||
return json
|
||||
|
3882
lib/fennel.lua
Normal file
3882
lib/fennel.lua
Normal file
File diff suppressed because it is too large
Load diff
780
lib/lume.lua
Normal file
780
lib/lume.lua
Normal file
|
@ -0,0 +1,780 @@
|
|||
--
|
||||
-- lume
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local lume = { _version = "2.3.0" }
|
||||
|
||||
local pairs, ipairs = pairs, ipairs
|
||||
local type, assert, unpack = type, assert, unpack or table.unpack
|
||||
local tostring, tonumber = tostring, tonumber
|
||||
local math_floor = math.floor
|
||||
local math_ceil = math.ceil
|
||||
local math_atan2 = math.atan2 or math.atan
|
||||
local math_sqrt = math.sqrt
|
||||
local math_abs = math.abs
|
||||
|
||||
local noop = function()
|
||||
end
|
||||
|
||||
local identity = function(x)
|
||||
return x
|
||||
end
|
||||
|
||||
local patternescape = function(str)
|
||||
return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")
|
||||
end
|
||||
|
||||
local absindex = function(len, i)
|
||||
return i < 0 and (len + i + 1) or i
|
||||
end
|
||||
|
||||
local iscallable = function(x)
|
||||
if type(x) == "function" then return true end
|
||||
local mt = getmetatable(x)
|
||||
return mt and mt.__call ~= nil
|
||||
end
|
||||
|
||||
local getiter = function(x)
|
||||
if lume.isarray(x) then
|
||||
return ipairs
|
||||
elseif type(x) == "table" then
|
||||
return pairs
|
||||
end
|
||||
error("expected table", 3)
|
||||
end
|
||||
|
||||
local iteratee = function(x)
|
||||
if x == nil then return identity end
|
||||
if iscallable(x) then return x end
|
||||
if type(x) == "table" then
|
||||
return function(z)
|
||||
for k, v in pairs(x) do
|
||||
if z[k] ~= v then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
return function(z) return z[x] end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function lume.clamp(x, min, max)
|
||||
return x < min and min or (x > max and max or x)
|
||||
end
|
||||
|
||||
|
||||
function lume.round(x, increment)
|
||||
if increment then return lume.round(x / increment) * increment end
|
||||
return x >= 0 and math_floor(x + .5) or math_ceil(x - .5)
|
||||
end
|
||||
|
||||
|
||||
function lume.sign(x)
|
||||
return x < 0 and -1 or 1
|
||||
end
|
||||
|
||||
|
||||
function lume.lerp(a, b, amount)
|
||||
return a + (b - a) * lume.clamp(amount, 0, 1)
|
||||
end
|
||||
|
||||
|
||||
function lume.smooth(a, b, amount)
|
||||
local t = lume.clamp(amount, 0, 1)
|
||||
local m = t * t * (3 - 2 * t)
|
||||
return a + (b - a) * m
|
||||
end
|
||||
|
||||
|
||||
function lume.pingpong(x)
|
||||
return 1 - math_abs(1 - x % 2)
|
||||
end
|
||||
|
||||
|
||||
function lume.distance(x1, y1, x2, y2, squared)
|
||||
local dx = x1 - x2
|
||||
local dy = y1 - y2
|
||||
local s = dx * dx + dy * dy
|
||||
return squared and s or math_sqrt(s)
|
||||
end
|
||||
|
||||
|
||||
function lume.angle(x1, y1, x2, y2)
|
||||
return math_atan2(y2 - y1, x2 - x1)
|
||||
end
|
||||
|
||||
|
||||
function lume.vector(angle, magnitude)
|
||||
return math.cos(angle) * magnitude, math.sin(angle) * magnitude
|
||||
end
|
||||
|
||||
|
||||
function lume.random(a, b)
|
||||
if not a then a, b = 0, 1 end
|
||||
if not b then b = 0 end
|
||||
return a + math.random() * (b - a)
|
||||
end
|
||||
|
||||
|
||||
function lume.randomchoice(t)
|
||||
return t[math.random(#t)]
|
||||
end
|
||||
|
||||
|
||||
function lume.weightedchoice(t)
|
||||
local sum = 0
|
||||
for _, v in pairs(t) do
|
||||
assert(v >= 0, "weight value less than zero")
|
||||
sum = sum + v
|
||||
end
|
||||
assert(sum ~= 0, "all weights are zero")
|
||||
local rnd = lume.random(sum)
|
||||
for k, v in pairs(t) do
|
||||
if rnd < v then return k end
|
||||
rnd = rnd - v
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.isarray(x)
|
||||
return type(x) == "table" and x[1] ~= nil
|
||||
end
|
||||
|
||||
|
||||
function lume.push(t, ...)
|
||||
local n = select("#", ...)
|
||||
for i = 1, n do
|
||||
t[#t + 1] = select(i, ...)
|
||||
end
|
||||
return ...
|
||||
end
|
||||
|
||||
|
||||
function lume.remove(t, x)
|
||||
local iter = getiter(t)
|
||||
for i, v in iter(t) do
|
||||
if v == x then
|
||||
if lume.isarray(t) then
|
||||
table.remove(t, i)
|
||||
break
|
||||
else
|
||||
t[i] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
|
||||
function lume.clear(t)
|
||||
local iter = getiter(t)
|
||||
for k in iter(t) do
|
||||
t[k] = nil
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.extend(t, ...)
|
||||
for i = 1, select("#", ...) do
|
||||
local x = select(i, ...)
|
||||
if x then
|
||||
for k, v in pairs(x) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
< |