forked from public/foundryvtt-wh4-lang-fr-fr
Update talents + beasts
This commit is contained in:
1
tools/beasts.json
Normal file
1
tools/beasts.json
Normal file
File diff suppressed because one or more lines are too long
263
tools/compendium_wfrp4e.bestiary.json
Normal file
263
tools/compendium_wfrp4e.bestiary.json
Normal file
@ -0,0 +1,263 @@
|
||||
{
|
||||
"label": "Bestiaire",
|
||||
"entries": [
|
||||
{
|
||||
"id": "Basilisk",
|
||||
"name": "Basilic",
|
||||
"description": "<p>Ces créatures reptiliennes à huit pattes sont solitaires et insaisissables. Considérées comme étant l'une des plus anciennes créatures du Chaos, on les dit emplies de tant de malveillance et de venin que même le sol sur lequel elles marchent est empoisonné. Leur morsure est également venimeuse, mais c'est leur regard pétrifiant qui est le plus à craindre. <br> Rarement rencontrés de nos jours, les basilics sont des ennemis des plus dangereux. Leurs glandes et leurs organes sont très prisés des Magiciens et Alchimistes. Ce qui fait que des chasseurs opportunistes écoutent souvent les rumeurs sur la présence de basilics dans les espaces sauvages du Vorbergland, et bien peu en reviennent.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Bear",
|
||||
"name": "Ours",
|
||||
"description": "<p>Arpentant les étendues sauvages du Reikland, les ours sont solitaires et généralement farouches, ne devenant agressifs que lorsque des intrus menacent leurs petits ou s'ils sont blessés. Quand la nourriture se raréfie, ils se rapprochent des villages non protégés et des voyageurs, en particulier ceux qui se montrent négligents avec les provisions.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Bloodletter of Khorne",
|
||||
"name": "Sanguinaire de Khorne",
|
||||
"description": "<p>Élus de Khorne, les sanguinaires règnent sur les champs de bataille du Vieux Monde, arrachant crânes et vies en l'honneur du Dieu Sang. Des dents pointues et acérées dépassent de leurs monstrueux visages cornus. Leur peau rouge-sang est dure comme l'airain, forgée sur l'enclume de guerres incessantes. Chaque sanguinaire porte une lame d'Enfer, diaboliquement tranchante et trempée dans le sang versé, qu'il manient avec témérité, se délectant du doux plaisir de la tuerie aveugle. </p>"
|
||||
},
|
||||
{
|
||||
"id": "Boar",
|
||||
"name": "Sanglier",
|
||||
"description": "<p>A la recherche de nourriture dans la forêt, les sangliers sont des créatures solitaires, qui lorsqu'ils sont acculés, se battent en utilisant leurs défenses aiguisées avec une ténacité qui stupéfait ceux qui n'y sont pas préparés. Bien que la plupart mesurent entre 1m50 et 1m80 de long, certains atteignent des proportions remarquables. Les plus grands d'entre eux sont très prisés par les Orcs, qui les utilisent comme de féroces montures de guerre.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Bog Octopus",
|
||||
"name": "Pieuvre des Tourbières",
|
||||
"description": "<p>Les pieuvres des tourbières se cachent dans les eaux peu profondes, généralement dans les marécages. Elles y attendent leurs proies dans une immobilité parfaite, capables de sentir les vibrations des créatures en approche, puis surgissant de l'eau boueuse, cherchant à agripper et à noyer en utilisant leurs robustes tentacules. Généralement marbrées de vert et de brun, les pieuvres des tourbières bénéficient d'un camouflage parfaitement adapté aux environnements marécageux, leurs immenses yeux limpides étant souvent le seul indice trahissant leur présence. Beaucoup possèdent des tentacules de six mètres et plus et un corps de plus d'un mètre quatre-vingts de long, mais des histoires prétendent qu'elles atteignent plusieurs fois cette taille, en particulier si elles ont un apport régulier supplémentaire en viande.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Bray-Shaman",
|
||||
"name": "Chaman-Bray",
|
||||
"description": "<p>Les Chaman-Brays sont nés avec la capacité de manier les pouvoirs du Chaos, qu'ils utilisent avec un talent terrifiant. Ils n'ont jamais besoin de se défendre eux-même contre les autres membres de leur horde, ce qui est exceptionnel chez les hommes-bêtes. Personne n'oserait les attaquer puisqu'ils représentent la volonté des Sombres dieux en personne.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Cairn Wraith",
|
||||
"name": "Spectre de Cairn",
|
||||
"description": "<p>Les Spectres de Cairn sont des esprits particulièrement puissants, les restes spectraux d'aspirants nécromanciens qui ont cherché à prolonger leur existence grâce à la magie sombre. De leur vivant, ils étaient obstinés ; dans la mort, leur volonté malveillante les pousse à exercer une terrible vengeance sur les âmes ardentes des vivants. Beaucoup de ces Spectres hantent les cairns couverts de brume qui parsèment le paysage de l'Empire, tels que les Hägercrybs déchues.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Cave Squig",
|
||||
"name": "Squig des Cavernes",
|
||||
"description": "<p><Les squigs sont de grandes créatures fongoïdes, en général de forme rondes, qui vivent sous terre dans les grottes les plus profondes et les plus sombres. Ils arborent une gueule béante et de grandes dents pointues. Ils sont prisés des gobelins pour leur chair et leur peau, mais aussi comme gardes ou animaux de compagnie./p>"
|
||||
},
|
||||
{
|
||||
"id": "Chaos Warrior",
|
||||
"name": "Guerrier du Chaos",
|
||||
"description": "<p>Énormes brutes lourdement cuirassées, affublées de pointes et d’impressionnants symboles de leurs dieux, les Guerriers du Chaos ne sont clairement plus des humains. Plus rien ne subsiste de leur existence passée, et ils n'existent que pour servir leur sombre chef et rien d'autre. Alors que la plupart des Guerriers du Chaos sont des maraudeurs de haut rang venant des régions loin au nord, quelques cultistes privilégiés peuvent se voir attribuer une armure du Chaos par les Sombres dieux, leur conférant une grande puissance, avec obligation de ne jamais l'ôter de leur vie. Comme peu de guerriers surpassent et qu'aucun chevalier n'est mieux protégé, leur espérance de vie demeure très longue.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Clanrat",
|
||||
"name": "Guerrier des Clans",
|
||||
"description": "<p>La plupart des Skavens sont originaires de l'un des nombreux et obscurs clans qui se chamaillent constamment pour des raisons diplomatiques, se poignardent dans le dos, ou encore déclarent la guerre aux autres. Ils agissent généralement sur les ordres de Skavens de statut supérieur, mais cherchent toujours un moyen d'obtenir une meilleure position, le plus souvent par trahison. Ils revêtent habituellement du cuir moisi ou des vêtements sales, avec des bouts de métal rouillé et terni leur servant d'armure. Les Guerriers des Clans sont souvent envoyés comme éclaireurs ou pillards pour récupérer des denrées, chercher des malepierres ou rafler des esclaves.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Crypt Ghoul",
|
||||
"name": "Goule de Crypte",
|
||||
"description": "<p>Parmi les créatures les plus pitoyables du Vieux Monde, les goules de cryptes sont des créatures voûtées et hideuses, à la peau cireuse et sale, et dont les dents jaunes bien aiguisées sont capables de déchirer la chair de leurs victimes. Les goules sont attirées par les énergies magiques de Shyish et de Dhar, ce qui signifie dans les faits qu'elles tournent autour vers des cimetières, des cryptes et des champs de bataille.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Cultist",
|
||||
"name": "Cultiste",
|
||||
"description": "<p>Pour ceux qui ne sont pas atteints par le fléau du Chaos, il ne représente qu'une abomination contre nature, mais pour ceux qu'il affecte, chaque pas vers la damnation, chaque idée les rapprochant du Chaos semble non seulement logique mais indispensable. Certains adeptes particulièrement dévoués des cultes proscrits reçoivent des \"dons\" de leurs dieux : d'immondes mutations qui les condamneront à mort si les Répurgateurs découvrent leur secret.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Daemonette of Slaanesh",
|
||||
"name": "Démonette of Slaanesh",
|
||||
"description": "<p>Comme toutes les créatures du Prince de la Souffrance et du Plaisir, les Démonettes de Slaanesh sont aussi belles qu'horribles. Leur allure surnaturelle défiant tout sens et toute rationalité, elles rendent leurs ennemis incapables de résister, tant ils sont charmés par la sensualité de leurs formes monstrueuses. Elles arborent une peau crémeuse et pâle et de grands yeux noirs comme du jais, ainsi qu'une épaisse chevelure flottant au vent et de couleur contre nature. Au lieu de mains, leurs bras sveltes se terminent par des griffes recourbées aux allures de pinces de crabe.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Demigryph",
|
||||
"name": "Demigriffon",
|
||||
"description": "<p>Avec leur tête d'aigle et leur corps de lion, les demigriffons sont de puissantes créatures au port altier. Ils arpentent les forêts et les prairies de l'Empire, généralement loin des habitations humaines, chassant seuls. Des demigriffons captifs sont utilisés par les Ordres de chevaliers les plus indomptables de l'Empire comme destrier de guerre. Contrairement aux plus grandes montures monstrueuses, généralement capturées jeunes et élevées en captivité, la capture d'un demigriffon adulte est un rite de passage dans certains ordres.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Dire Wolf",
|
||||
"name": "Loup funeste",
|
||||
"description": "<p>Quand la terre restitue les morts, il n'y a pas que les humains qui se relèvent : parodies macabres de loups géants aux yeux rougeoyants, aux lambeaux de chair tombant de leurs carcasses putréfiées, les Loups funestes rôdent la nuit pour le compte de leurs maîtres nécromanciens. Dans le Reikland, on dit qu'ils parcourent les forêts hantées des Hägercrybs lorsque Morrlieb est pleine, à la recherche d'une proies pour assouvir leur insatiable faim, rongés par un appétit de chair et un instinct accru pour l'odeur du sang.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Dog",
|
||||
"name": "Chien",
|
||||
"description": "<p>Les chiens sont élevés pour une myriade de raisons à travers le Vieux Monde. Alors que les toutous choyés des courtisans d'Altdorf ne sont une menace que pour la dignité d'un Joueur, les races plus grandes, y compris celles élevées pour la guerre et les combats de chiens, peuvent représenter une menace considérable.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Dragon",
|
||||
"name": "Dragon",
|
||||
"description": "<p>Les dragons régnaient sur les cieux bien avant que les anciennes races n'arpentent le Monde Connu. Même si les Dragons d'aujourd'hui ne sont plus que l'ombre de leurs frères, ils demeurent parmi les créatures connues les plus puissantes de l'Empire. Les quelques anciens Dragons survivants sont immenses et sortent rarement de leur léthargie. S'ils prennent ombrage de la présence d'un petit groupe d'aventuriers, ces derniers ne pourront pas faire grand-chose pour lui résister.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Fenbeast",
|
||||
"name": "Bête des marais",
|
||||
"description": "<p>Animés par les lanceurs de Sorts à partir d'immondices des marais et des tourbières, les Bêtes des Marais ne sont que des automates parfaitement stupides créés par magie. Vaguement humanoïdes, ils sont constitués de boue, d'os, de branches et de mucus, et nécéssitent d'importantes quantités de magie pour conserver leur intégrité. Parfois, ils sont animés par des Magicien spécialisés dans la Magie de Jade pour servir de gardes du corps ou pour accomplir une tâche spécifique nécessitant de la force brute ou un meurtre aveugle. Ils s'animent parfois de façon autonome, engendrés dans des bassins d'eau stagnante où le flux de Magie a été corrompu. Le Collège de Jade d'Altdorf conserve une vingtaine de ces créatures comme bêtes de somme, qui effectuent es corvées pour les Magisters supérieurs, maintenu par la focalisation incessante de douzaines d'apprentis.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Fimir",
|
||||
"name": "Fimir",
|
||||
"description": "<p>Les fimirs sont des créatures reptiliennes discrètes, pourvues d'un seul oeil. Ils chassent dans les recoins sombres des marais et des tourbières de l'ouest du Reikland, mais fuient la lumière du soleil, n'apparaissant habituellement qu'à l'aube ou au crépuscule, ou sous le couvert de la brume et du brouillard pour traquer des victimes à des fins mystérieuses. Des sorcières humaines inconscientes ou suffisamment désespérées pour se mêler des affaires des Démons ont été appelées pour rechercher un Fimirs dans le but de mettre au jour ses pernicieux mystères, car on prétend qu'elles invoquent et contrôlent ces entités. Et quand à savoir s'il est plus risqué de traiter avec les Puissances de la Ruine ou avec les Fimir, à chacun de le décider. Les Fimirs forment une société matriarcale. Le chef de chaque clan est une puissante sorcière appelée Meargh, aidée par un groupe de lanceurs de Sorts inférieurs appelé Dirach. La majorité des membres du clan sont de humbles Sheals, qui sont protégés par une caste de guerriers, les Fimms, au queues garnis de pointes et de boules osseues, utilisés pour briser les membres des adversaires imprudents.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Fr'hough Mournbreath",
|
||||
"name": "Fr'hough Mournbreath"
|
||||
},
|
||||
{
|
||||
"id": "Ghost",
|
||||
"name": "Fantôme",
|
||||
"description": "<p>Les fantômes sont les esprits d'âmes tourmentées, mortes alors que leurs affaires terrestres n'étaient pas réglées. Tout comme les squelettes et les zombies, les fantômes peuvent être invoqués par des vampires ou des nécromanciens, ou peuvent hanter des régions imprégnées de Dhar. Dans des cas exceptionnels, des esprits particulièrement déterminés peuvent se frayer un chemin depuis le royaume de Morr pour poursuivre leurs propres affaires, bien que de tels événements attirent rapidement l'attention du culte de Morr, ou des Magisters de l'Ordre de'Améthystes. Lorsqu'ils sont invoqués grâce aux arts nécromantiques, les fantômes se regroupent, formant de grandes armées d'esprits qui fondent sur leurs ennemis, répandant la peur et le désarroi dans leur passage.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Giant",
|
||||
"name": "Géant",
|
||||
"description": "<p>Les géants sont des créatures solitaires qui évitent généralement la civilisation. La plupart chassent dans les endroits éloignés et montagneux entourant l'Empire, se cachent dans des grottes et des ruines oubliées, loin des peuples plus petits, bien qu'ils migrent parfois dans les contreforts à la recherche de nourriture. Ils ont la réputation d'être belliqueux et féroces, à cause de leur taille impressionnante, de leur régime alimentaire à base de bétail et de leur association avec des armées de Peaux Vertes en maraude qui les asservissent. En réalité, beaucoup de géants ont un bon fond, sont en proie à des crises de colère et défendent farouchement leur vie privé, mais ne sont pas nécessairement hostiles aux autres.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Giant Rat",
|
||||
"name": "Rat géant",
|
||||
"description": "<p>Les rats s'infiltrent partout et sont particulièrement répandus dans les cités et les villes. Plus la population est dense, plus les rats s'entassent avec eux, ce qui est particulièrement néfaste quand les créatures sont porteuses de maladies. Généralement particulièrement petits, ils peuvent atteindre des proportions monstrueuses, et l'on rapporte la présence de rats sous Altdorf, de la taille d'un humain voire plus grands. On utilise des pièges à rats pour les garder sous contrôle.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Giant Spider",
|
||||
"name": "Araignée Géante",
|
||||
"description": "<p>Les araignées géantes se tapissent au plus profond des forêts et des grottes de l'Empire, mais elles peuvent vivre n'importe où, y compris dans les greniers poussiéreux et les caves sombres. La plupart capturent leurs proies à l'aide des toiles solides avant de leur injecter du venin. Bien que la plupart aient la taille d'un grand rat, certaines espèces d'araignées géantes sont effroyablement grandes. Les gobelins des forêts capturent souvent de grands spécimens pour en faire des montures.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Goblin",
|
||||
"name": "Gobelin",
|
||||
"description": "<p>Des gobelins, sire ! Par milliers!'</p>\n<p>- Lieutenant Bromkopf, 24ème régiment de Fantassins Reiklander</p>\n<p> </p>\n<p>L'instinct de conservation d'un gobelin, rachitique, malveillant, mais également agile et intelligent, ne doit jamais être sous-estimé. Ces créatures sont néanmoins lâches et se regroupent donc facilement si le nombre leur procure un avantage écrasant. Les gobelins rejoignent souvent les armées d'orcs - pas toujours par choix - s'appropriant opportunément une partie du butin de guerre pendant que les autres prennent part au plus gros des combats.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Gor",
|
||||
"name": "Gor",
|
||||
"description": "<p>Les Gors, les hommes-bêtes les plus courants, hantent presque chaque forêt du Vieux Monde. Leur apparence varie considérablement, mais tous allient des traits bestiaux et humains, souvent avec la tête et les jambes d'un bouc et le torse et les bras d'un humains. La seule caractéristique commune à tous les Gors est une grande paire de cornes - les plus grandes sont les meilleures, car elles indiquent leur statut parmi les hommes-bêtes - un trait qui les différencie des Ungor et des Changepeaux. Les plus grands Gors sont connus sous le nom de Bestigors.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Griffon",
|
||||
"name": "Griffon",
|
||||
"description": "<p>Avec les pattes avant et les ailes d'un aigle et l'arrière-train d'un grand chat, les Griffons sont des bêtes élégantes au port naturellement altier et majestueux. Ils nichent dans les montagnes qui entourent l'Empire et sont des tueurs rapides et efficaces, peu enclins à la destruction aveugle de créatures, contrairement aux manticores ou aux hippogriffes. Peut-être à cause de cette apparente noblesse, l'image du Griffon est extraordinairement populaire au sein de l'Empire, utilisée sur les blasons, dans l'iconographie religieuse et comme symbole de l'Empire lui-même. Les Griffons sont aussi l'une des bêtes les plus intelligentes. S'ils sont capturés relativement jeunes et soumis à un entraînement approprié, ils peuvent être extrêmement loyaux, capables d'anticiper et d'obéir à une grande variété d'ordres. Ils sont tellement prisés que les chasseurs meurent régulièrement en essayant de se procurer un œufs de Griffons pour le revendre. Le Griffon le plus célèbre du Reikland est sans doute Griffe-de-Mort. Installé dans le zoo impérial d'Altdorf, Griffe-de-Mort appartenait au prince héritier du Reikland et aurait parait-il éclos dans les mains de l'empereur Karl-Franz en personne - ce qui a causé l'impression de toutes sortes de dessins humoristiques dans des pamphlets séditieux.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Hippogryph",
|
||||
"name": "Hippogryffe",
|
||||
"description": "<p>Particulièrement féroces et territoriaux, les hippogriffes possèdent une tête et des ailes d'aigle, le buste d'un lion, et l'arrière-train d'un cheval. Issus le plus souvent des Montagnes Grises, ils attaquent sans provocation, presque imprudents dans leur rage, déchirant la chair en lambeaux pour la dévorer plus tard. Leur rage est telle que des hardes entiers de bétail peuvent être anéantis par un seul Hippogriffe, qui s'enfuit ensuite avec ses proies préférées, abandonnant les restes aux charognards. N'étant pas foncièrement intelligents, lors de la chasse, les hippogriffes ont tendance à tuer tout ce qu'ils voient. Supposant que tout ce qui ne voient plus disparaît à jamais, les victimes réelles des hippogriffes sont relativement peu nombreuses, puisqu'il suffit de trouver une bonne cachette pour leur échapper.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Horse",
|
||||
"name": "Cheval",
|
||||
"description": "<p>Les chevaux sont élevés pour différents usages, servant de monture rapide aux Messagers, de destriers robustes aux Chevaliers et de chevaux de traits pour les fermiers. Ils sont tellement utilisés que le commerce de chevaux est presque une compétition sportive au Reikland. Les maquignons (les vendeurs de chevaux) sans scrupules cherchent à se faire de l'argent sur le dos d'acheteurs ignorants, en lissant la robe et limant les dents des vieux chevaux, ou en leur bourrant les naseaux de petits chiffons pour éponger le mucus. Un acheteur non averti devrait donc se montrer prudent.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Hydra",
|
||||
"name": "Hydre",
|
||||
"description": "<p>L'Hydre possède plusieurs têtes semblables à celles d'un lézard et un corps massif qui supporte un entrelacs de cous et de têtes qui soufflent une fumée incandescente et mordent férocement. Créature étonnamment tenace et furtive, les hydres traquent leurs proies sur des kilomètres, mais elles perdent trop souvent patience et chargent, en rugissant de toutes leurs têtes.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Jabberslythe",
|
||||
"name": "Jabberslythe",
|
||||
"description": "<p>Le Jabberslythe est une ancienne créature du Chaos qui se cache dans les profondeurs des forêts les plus sombres. Créature démente, le Jabberslythe est un ignoble mélange de crapaud, de dragon de vase et d'insecte, le tout irrigués de sang noir corrosif qui jaillit à la moindre blessure. Pire encore, tous ceux qui regardent cette horreur perdent l'esprit, baragouinant, se griffant les yeux et riant hystériquement, faisant d'eux des proies facile pour le Jabberslythe. De plus, il dispose d'une langue collante capable de fouetter et de ramener son prochain repas dans sa gueule béante. En accord avec son apparence, la créature se déplace de façon gauche et balourde, et possède même des ailes, bien trop petites pour réussir à son impressionnant gabarit.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Manticore",
|
||||
"name": "Manticore",
|
||||
"description": "<p><em>\"Quand je voyageais avec les elfes d'Ulthuan, j'ai vu beaucoup de choses qui sidéreraient la plupart des gens. Une fois, j'ai aperçu une manticore, avec au beau milieu des sa crinière de lion un visage de haut elfe ! Je suppose que c'était moins une Manticore qu'une Elfticore.\"</em></p>\n<p>- Adhemar Fitztancred, Gardien Gris, conteur et menteur</p>\n<p> Heureusement assez rares, les Manticores sont féroces à l’extrême, animées par la volonté de débarrasser leur territoire, avec une extraordinaire brutalité, des prédateurs rivaux. Cela signifie que se trouver sur le territoire d'une Manticore ne passe pas inaperçu, car des corps d'autres monstres jonchent le sol. La créature possède la tête et le corps déformé d'un grand chat (bien que parfois son visage apparaisse un peu trop humain), les ailes d'une chauve-souris et une queue semblable à un fouet hérissé de barbelés.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Minotaur",
|
||||
"name": "Minotaure",
|
||||
"description": "<p>Les Minotaures semblables à des taureaux, énormes et massifs, dominent même les plus grands Bestigors. Les hordes possédant un grand nombre de Minotaures se considèrent comme particulièrement bénis par les Sombres Dieux. Les hommes-bêtes se rassemblent autour des Minotaures car leur présence imposante insuffle du courage même aux plus petits d'entre eux.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Mutant",
|
||||
"name": "Mutant",
|
||||
"description": "<p>L'un des destins les plus tragiques d'un Humain est de succomber à l'influence mutagène du Chaos. Cela peut se produire sans raison aucune, et même des bébés peuvent naître mutants. Lorsque cela se produit, beaucoup de parents ne trouvent pas la force d'assassiner leurs enfants, mais les abandonnent plutôt dans les bois, pour qu'ils meurent ou soient recueillis par d'autres mutants ou hommes-bêtes. Peu importe qu'ils soient innocents, tous les mutants sont une source de terreur pour le commun des mortels, et la plupart se tournent vers les Sombres dieux, abandonnés et aigris, ou en finissent avant qu'il ne soit trop tard.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Ogre",
|
||||
"name": "Ogre",
|
||||
"description": "<p><em><span class=\"fontstyle0\"><span class=\"fontstyle0\">'Oui, je les ai convoqués parce que j'avais besoin de renforts. Oui, je sais, ils mangent votre bétail. Et oui, je sais que les ogres ont un gros appétit. J'ai mis une annonce au poste de garde local, offrant une petite somme à toute personne ayant les moyens de les faire partir. Cela devrait être bien suffisant\". </span></span></em></p>\n<p><span class=\"fontstyle0\"><span class=\"fontstyle0\" style=\"font-size : 10pt ;\">- Augustus von Raushvel, Baron de Raush Vale</span> <br style=\"font-variant-numeric : normal ; font-variant-east-asian : normal ; hauteur de ligne : normal ; text-align : -webkit-auto ; text-size-adjust : auto ;\"></span></p>\n<p> </p>\n<p><span class=\"fontstyle0\">Grands, bruyants, brutaux et extrêmement violents, les ogres sont animés par le besoin de remplir leur énorme ventre musclé. Ils manquent d'astuce ou d'intelligence pour le faire avec subtilité, optant d'emblée pour la force dans la plupart des situations. Originaires des terres situées très à l'est, les Ogres sont répandus dans le Vieux Monde car ils adorent errer, toujours à la recherche de nouvelles proies. Au cours de leurs voyages à but alimentaires, longs de plusieurs décennies, ils mettent tout en oeuvre pour s'intégrer, portant des vêtements de la région et suivant les coutumes locales qu'ils comprennent, car cela leur permet d'attirer leur prochaine proie.</span></p>\n<p><span class=\"fontstyle0\"><br>Dans l'Empire, ils servent souvent dans l'Armée et sont souvent aperçus dans les grandes villes, où ils sont recrutés pour leurs muscles. La guilde des Vieux Halfling (des entrepreneurs en construction) s'est effectivement accaparé le travail bon marché des ogres dans nombre de villes et cités de l'Empire, source d'un grand mécontentement pour ses rivaux.</span></p>\n<p> </p>\n<h3>Halflings et ogres</h3>\n<p><span class=\"fontstyle0\">Personne ne sait pourquoi, mais les Halflings et les Ogres s'entendent bien. Beaucoup d'anciens des clans Halfling ont un ou deux gardes du corps ogres, et on dit que l'ancien de la Moot voyage rarement sans son vieil ami Zorarth Mord-Gigot, un ogre aux cheveux blancs qui vit dans l'empire depuis près d'un siècle. En retour, de nombreuses bandes de mercenaires ogres emploient des Halfling comme cuisiniers. Cependant, cette relation ne fonctionne pas toujours. Golgfag Mang'homme, le capitaine d'un groupe de mercenaire du nom de (quelle imagination !) Mang'hommes de Golgfag, employait notoirement un petit clan de Halflings aux cuisines, pour lui et ses hommes après les grandes victoires. Jusqu'à ce que Golgfag réalise que les cuisiniers avaient un bien meilleur goût que la nourriture qu'ils préparaient...</p>"
|
||||
},
|
||||
{
|
||||
"id": "Orc",
|
||||
"name": "Orc",
|
||||
"description": "<p>Nous êtres les meilleurs. Nous pas êtres des foutus Gobbos verts ou des trolls stupides, nous être plus fort ! Et si quelqu'un dit que nous pas fort, nous taper dur sur foutue tête.\n<p>- Gurkk Chopecrân', Chef Orc</p>\n<p> </p>\n<p>Les Orcs sont mauvais, brutaux, belliqueux et presque immunisés contre la douleur. Ils possèdent un corps massif et de larges épaules puissantes et ne laisseront pas une chose aussi insignifiante que la perte d'un bras faire obstacle à une bonne bagarre. Ils sont taillés pour le combat et n'aiment rien de plus au monde. Quand ils n'ont pas d'ennemis à combattre, ils s'en prennent à des groupes rivaux de Peaux-vertes. S'il n'y a pas de groupe rival, ils se battront entre eux. Bien que moins nombreux que les gobelins, ils sont plus grands et plus robustes, et toutes les occasions sont bonnes pour le faire savoir. Les Orcs peuvent atteindre des tailles prodigieuses, et les plus grands, les plus forts, les plus robustes et les plus agressifs d'entre eux se voient par conséquent accorder plus de prestige : la force entraîne le pouvoir dans leur société guerrière. Certains Orcs chevauchent d'énormes sangliers pendant les batailles, un spectacle assez terrifiant.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Pegasus",
|
||||
"name": "Pégase",
|
||||
"description": "<p>Les Pégases sont de magnifiques chevaux blancs pourvus d'ailes de cygne. Ce sont des voltigeurs acharnés et inépuisables qui se déplacent en grandes hordes dans les haute montagne, semblant prendre beaucoup de plaisir à virevolter dans les courants d'air chauds tourbillonnants. Les pégases font de parfaits destriers et beaucoup de guerriers ou d'éclaireurs ont tenté d'en capturer un. Ils sont très intelligents, et certains pensent qu'ils ne se laissent capturer que s'ils le veulent bien, ce qui a fait naître toutes sortes de légendes romantiques fantaisistes insistant sur le fait que seuls les plus dignes ou les plus vertueux pouvaient être choisis.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Pigeon",
|
||||
"name": "Pigeon",
|
||||
"description": "<p>Les pigeons sont élevés dans tout le Reikland pour transporter des messages de toutes sortes. Récemment, ils ont gagné en renommé pour le transport de chargements plus mortels, car l'oiseau facilement accessible est devenu populaire parmi les ingénieurs, qui les utilisent pour déployer leurs \"bombes-pigeons\" à des degrés divers de réussite.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Rat Ogre",
|
||||
"name": "Rat Ogre",
|
||||
"description": "<p>Les Rats Ogres sont des brutes imposantes, élevées dans les cavernes sombres de l'Empire souterrain par les Maîtres corrupteurs du Clan Molder. Ils sont stupides, mais intrépides et implacables au combat lorsqu'ils sont dirigés par leurs maîtres Skavens. Rarement rencontrés seuls, ils ont tendance à accompagner et servir de gardes du corps à des prophètes gris, ou autres Skavens de haut rang.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Skeleton",
|
||||
"name": "Squelette",
|
||||
"description": "<p>Les squelettes sont les restes décharnés de ceux qui sont morts depuis longtemps, réanimés, parodies de vivants, par une magie sombre pour arpenter la terre. Ceux qui sont morts sans recevoir le repos accordé par les rituels de Morr, le Dieu de la mort, peuvent être ressuscités sous cette forme par un nécromancien suffisamment puissant. Totalement dépourvus d'intelligence, les squelettes combattent jusqu'à ce que leurs os soient réduits en miettes. Leur ténacité n'a rien à voir avec du courage, ils n'ont tout simplement peur de rien et ne peuvent pas être tués puisqu'ils ne sont vivants.</p>\n<h3>Options : Des Os</h3>\n<p>Pour Refléter la nature osseuse des Squelettes, vous pouvez imposer une pénalité de -1 Dégâts aux blessures infligées par les armes n'ayant pas le trait Assommante.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Slenderthigh Whiptongue",
|
||||
"name": "Slenderthigh Whiptongue"
|
||||
},
|
||||
{
|
||||
"id": "Snake",
|
||||
"name": "Serpent",
|
||||
"description": "<p>On trouve des serpents dans tout l'Empire, en particulier dans les forêts profondes. La plupart sont inoffensifs, mais certains utilisent un venin mortel ou enserrent leurs victimes jusqu'à ce qu'elles meurent par suffocation. Comme beaucoup de créatures du Vieux Monde, ils peuvent atteindre des proportions gigantesques, représentant un réel danger même pour le plus fort des mercenaires.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Snotling",
|
||||
"name": "Snotling",
|
||||
"description": "<p>Créatures écervelées, s'apparentant à des chiots excités et incontrôlables, les Snotlings sont des charognards et des imitateurs-nés, ramassant des os et des choses brillantes partout où ils passent, ou copiant les actions de tout ce qu'ils voient. Si les gobelins ou les orcs les poussent au conflits, ils se battent en nuées nauséabondes, essayant de submerger leurs ennemis par le poids du nombre. Pour cela, ils trouvent toutes sortes de substances répugnantes et toxiques, comme des champignons vénéneux et des déjections, à lancer sur leurs ennemis.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Stormvermin",
|
||||
"name": "Vermine de choc",
|
||||
"description": "<p>Les combattants d'élite des Skavens sont les Vermines de choc : plus grands, plus forts, plus endurcis et plus disciplinés que les Guerrier des clans. Ils servent de base à tout assaut majeur et constituent les gardes du corps de Skavens importants. Les Vermines de chocs sont généralement lourdement et cuirassées, arborant les combinaisons d'armes favorites de leur clan.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Tomb Banshee",
|
||||
"name": "Banshee",
|
||||
"description": "<p>Les Banshees sont les vestiges spectraux de sorcières autrefois puissantes dont les esprits sont imprégnés de l'énergie fétide de Dhar. Leur après-vie sans repos est tourmentée par le chagrin et l'amertume, un vide béant dans leurs âmes qui les pousse à émettre des hurlements déchirants, assez terrifiants et puissants pour plonger dans la folie ceux qui l'entendent, ou même arrêter leur cœur.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Troll",
|
||||
"name": "Troll",
|
||||
"description": "<p>Les trolls sont des créatures sales et immondes qui infestent tous les coins du Vieux Monde. Prompts à s'adapter à leur environnement, quel que soit leur type, ils sont tous énormes et imposants. Sombres et mus par leurs pulsions alimentaires, ils aiment tout de même amasser des biens et leurs repaires peuvent renfermer des trésors composés d'objets utiles et de valeur... et des restes macabres de leurs repas. Bien qu'il existe beaucoup d'espèces de Trolls, ils partagent tous des traits communs : ils sont généralement extrêmement stupides, ce qui signifie que tout ennemi à l'esprit vif d'esprit peut avoir un avantage sur eux ; ils peuvent régénérer, ce qui les rend extrêmement difficiles à tuer ; et ils sont capables de régurgiter leur dernier repas à volonté, en vomissant de la bile âcre sur une distance particulièrement impressionnantes - bien qu'ils répugnent à le faire, car la faim les tenaille alors.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Ungor",
|
||||
"name": "Ungor",
|
||||
"description": "<p>Les Ungors possèdent des cornes vestigiales ou très courtes, et il sont donc à peine considérés comme des \"Gor\" par le reste de la horde. Certains possèdent même des visages semblables à ceux des humains, faisant d'eux des infiltrés efficace, mais aussi des cibles faciles aux moqueries. En effet, les Ungors sont durement traités par les Gors, et sont souvent chétifs et mal nourris par rapport à leurs frères aux plus grandes cornes, ce qui en fait des créatures aigries, enclines à reporter leur jalousie sur les autres.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Vampire",
|
||||
"name": "Vampire",
|
||||
"description": "<p>Les vampires se considèrent comme les rois de la nuit. Beaucoup peuvent passer pour des humains, certains opérant même durant de longues périodes parmi les vivants. Malgré leur apparence extérieure d'humains, aucun coeur ne bat sous leur peau pâle, et au lieu des faims des mortels, ils souffrent d'une soif incessante de sang. Tous les vampires du Vieux Monde sont en fin de compte tous issus de lignées anciennes, apparues il y a des millénaires au Sud. Beaucoup sont extrêmement fiers de leur héritage, des traits et traditions qui les différencient des autres. Les vampires de lignées différentes sont souvent de grands rivaux, mais ils sont assez intelligents pour se rassembler quand cela devient nécessaire pour faire face aux ennemis les plus importants.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Varghulf",
|
||||
"name": "Chauve-Souris Vampire",
|
||||
"description": "<p>La plupart des vampires dissimulent leur besoin de sang sous une apparence de civilité et de savoir-vivre, se définissant eux-même comme les aristocrates des morts-vivants. Cependant, certains fuient leur côté humain et embrassent la bête qui les habitent. Ces Chauve-Souris Vampires sont des créatures cruelles et sauvages, abandonnées à l'autosatisfaction animale. Elles se manifestent sous la forme de grandes bêtes aux allures de chauve-souris, se délectant de leur besoin primaire de sang.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Wolf",
|
||||
"name": "Loup",
|
||||
"description": "<p>Les loups chassent généralement en meute et ont la réputation d'être des chasseurs tenaces, poursuivant leurs proies sur des dizaines de kilomètres. Plusieurs espèces rôdent dans le Reikland, dont les redoutables loups géants, qui sont capturés et élevés par les gobelins qui les utilisent comme gardes ou montures.</p>"
|
||||
},
|
||||
{
|
||||
"id": "Wyvern",
|
||||
"name": "Vouivre",
|
||||
"description": "Bien que les incultes les confondent souvent à tort avec des dragons, au-delà de quelques similitudes superficielles, les vouivres nauséabondes n'ont rien de commun avec ces dignes créatures. Charognards lâches et malvoyants, elles ont tendance à assouvir leur énorme appétit en choisissant des créatures sans défense - principalement des moutons et des chèvres -, évitant autant que possible le combat direct. Contrairement aux autres monstres des Montagnes, les vouivres ne sont pas particulièrement territoriales, et se déplacent généralement lorsque leurs territoires de chasse sont envahis."
|
||||
},
|
||||
{
|
||||
"id": "Zombie",
|
||||
"name": "Zombie",
|
||||
"description": "<p>Comme les squelettes, les zombies sont des créatures mortes-vivantes réanimées et maintenues \"en vie\" par d'ignobles magies. Contrairement aux Squelettes, ils sont morts si récemment qu'une grande partie de leur corps subsiste encore, la chair malade et pourrissante s'affaissant sur leurs organes boursouflés et envahis par les vers. Quand ils combattent, leur chair et leurs organes se détachent de leurs os, libérant des effluves nauséabonde et toxiques, suffisamment puissantes pour retourner l'estomac de quiconque, à part, peut être, les soldats les plus endurcis.</p>"
|
||||
}
|
||||
]
|
||||
}
|
29
tools/foundry_wh4_bestiary_merge.lua
Normal file
29
tools/foundry_wh4_bestiary_merge.lua
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
package.path = package.path .. ";luajson/?.lua"
|
||||
local JSON = require"json"
|
||||
|
||||
local beastref_f = "../compendium/wfrp4e.bestiary.json"
|
||||
local beastdescr_f = "compendium_wfrp4e.bestiary.json"
|
||||
|
||||
local f1 = io.open(beastref_f)
|
||||
local strjson = f1:read("*a")
|
||||
local beastref = JSON.decode(strjson)
|
||||
|
||||
local f2 = io.open(beastdescr_f)
|
||||
strjson = f2:read("*a")
|
||||
f2:close()
|
||||
local beastdescr = JSON.decode(strjson)
|
||||
|
||||
|
||||
for _, beasttext in pairs(beastdescr.entries) do
|
||||
for _, beastgood in pairs(beastref.entries) do
|
||||
if beasttext.id == beastgood.id then
|
||||
beastgood.description = beasttext.description
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local jsonout = JSON.encode( beastref )
|
||||
local fout = io.open("beasts.json", "w+")
|
||||
fout:write( jsonout )
|
||||
fout:close()
|
625
tools/lpeg/lpcap.lua
Normal file
625
tools/lpeg/lpcap.lua
Normal file
@ -0,0 +1,625 @@
|
||||
--[[
|
||||
LPEGLJ
|
||||
lpcap.lua
|
||||
Capture functions
|
||||
Copyright (C) 2014 Rostislav Sacek.
|
||||
based on LPeg v1.0 - PEG pattern matching for Lua
|
||||
Lua.org & PUC-Rio written by Roberto Ierusalimschy
|
||||
http://www.inf.puc-rio.br/~roberto/lpeg/
|
||||
|
||||
** 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.
|
||||
**
|
||||
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
|
||||
--]]
|
||||
local ffi = require "ffi"
|
||||
|
||||
local Cclose = 0
|
||||
local Cposition = 1
|
||||
local Cconst = 2
|
||||
local Cbackref = 3
|
||||
local Carg = 4
|
||||
local Csimple = 5
|
||||
local Ctable = 6
|
||||
local Cfunction = 7
|
||||
local Cquery = 8
|
||||
local Cstring = 9
|
||||
local Cnum = 10
|
||||
local Csubst = 11
|
||||
local Cfold = 12
|
||||
local Cruntime = 13
|
||||
local Cgroup = 14
|
||||
|
||||
local MAXSTRCAPS = 10
|
||||
|
||||
local pushcapture
|
||||
local addonestring
|
||||
|
||||
|
||||
-- Goes back in a list of captures looking for an open capture
|
||||
-- corresponding to a close
|
||||
|
||||
local function findopen(cs, index)
|
||||
local n = 0; -- number of closes waiting an open
|
||||
while true do
|
||||
index = index - 1
|
||||
if cs.ocap[index].kind == Cclose then
|
||||
n = n + 1 -- one more open to skip
|
||||
elseif cs.ocap[index].siz == 0 then
|
||||
if n == 0 then
|
||||
return index
|
||||
end
|
||||
n = n - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function checknextcap(cs, captop)
|
||||
local cap = cs.cap;
|
||||
-- not a single capture? ((cap)->siz != 0)
|
||||
if cs.ocap[cap].siz == 0 then
|
||||
local n = 0; -- number of opens waiting a close
|
||||
-- look for corresponding close
|
||||
while true do
|
||||
cap = cap + 1
|
||||
if cap > captop then return end
|
||||
if cs.ocap[cap].kind == Cclose then
|
||||
n = n - 1
|
||||
if n + 1 == 0 then
|
||||
break;
|
||||
end
|
||||
elseif cs.ocap[cap].siz == 0 then
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
cap = cap + 1; -- + 1 to skip last close (or entire single capture)
|
||||
if cap > captop then return end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
-- Go to the next capture
|
||||
|
||||
local function nextcap(cs)
|
||||
local cap = cs.cap;
|
||||
-- not a single capture? ((cap)->siz != 0)
|
||||
if cs.ocap[cap].siz == 0 then
|
||||
local n = 0; -- number of opens waiting a close
|
||||
-- look for corresponding close
|
||||
while true do
|
||||
cap = cap + 1
|
||||
if cs.ocap[cap].kind == Cclose then
|
||||
n = n - 1
|
||||
if n + 1 == 0 then
|
||||
break;
|
||||
end
|
||||
elseif cs.ocap[cap].siz == 0 then
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
cs.cap = cap + 1; -- + 1 to skip last close (or entire single capture)
|
||||
end
|
||||
|
||||
|
||||
-- Push on the Lua stack all values generated by nested captures inside
|
||||
-- the current capture. Returns number of values pushed. 'addextra'
|
||||
-- makes it push the entire match after all captured values. The
|
||||
-- entire match is pushed also if there are no other nested values,
|
||||
-- so the function never returns zero.
|
||||
|
||||
local function pushnestedvalues(cs, addextra, out, valuetable)
|
||||
local co = cs.cap
|
||||
cs.cap = cs.cap + 1
|
||||
-- no nested captures?
|
||||
if cs.ocap[cs.cap - 1].siz ~= 0 then
|
||||
local st = cs.ocap[co].s
|
||||
local l = cs.ocap[co].siz - 1
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = cs.s and cs.s:sub(st, st + l - 1) or cs.stream(st, st + l - 1)
|
||||
return 1; -- that is it
|
||||
else
|
||||
local n = 0;
|
||||
while cs.ocap[cs.cap].kind ~= Cclose do -- repeat for all nested patterns
|
||||
n = n + pushcapture(cs, out, valuetable);
|
||||
end
|
||||
-- need extra?
|
||||
if addextra or n == 0 then
|
||||
local st = cs.ocap[co].s
|
||||
local l = cs.ocap[cs.cap].s - cs.ocap[co].s
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = cs.s and cs.s:sub(st, st + l - 1) or cs.stream(st, st + l - 1)
|
||||
n = n + 1
|
||||
end
|
||||
cs.cap = cs.cap + 1 -- skip close entry
|
||||
return n;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Push only the first value generated by nested captures
|
||||
|
||||
local function pushonenestedvalue(cs, out, valuetable)
|
||||
local n = pushnestedvalues(cs, false, out, valuetable)
|
||||
for i = n, 2, -1 do
|
||||
out.out[out.outindex] = nil
|
||||
out.outindex = out.outindex - 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Try to find a named group capture with the name given at the top of
|
||||
-- the stack; goes backward from 'cap'.
|
||||
|
||||
local function findback(cs, cap, name, valuetable)
|
||||
-- repeat until end of list
|
||||
while cap > 0 do
|
||||
cap = cap - 1
|
||||
local continue
|
||||
if cs.ocap[cap].kind == Cclose then
|
||||
cap = findopen(cs, cap); -- skip nested captures
|
||||
elseif cs.ocap[cap].siz == 0 then
|
||||
continue = true -- opening an enclosing capture: skip and get previous
|
||||
end
|
||||
if not continue and cs.ocap[cap].kind == Cgroup and cs.ocap[cap].idx ~= 0 then
|
||||
local gname = valuetable[cs.ocap[cap].idx] -- get group name
|
||||
-- right group?
|
||||
if name == gname then
|
||||
return cap;
|
||||
end
|
||||
end
|
||||
end
|
||||
error(("back reference '%s' not found"):format(name), 0)
|
||||
end
|
||||
|
||||
|
||||
-- Back-reference capture. Return number of values pushed.
|
||||
|
||||
local function backrefcap(cs, out, valuetable)
|
||||
local curr = cs.cap;
|
||||
local name = valuetable[cs.ocap[cs.cap].idx] -- reference name
|
||||
cs.cap = findback(cs, curr, name, valuetable) -- find corresponding group
|
||||
local n = pushnestedvalues(cs, false, out, valuetable); -- push group's values
|
||||
cs.cap = curr + 1;
|
||||
return n;
|
||||
end
|
||||
|
||||
|
||||
-- Table capture: creates a new table and populates it with nested
|
||||
-- captures.
|
||||
|
||||
local function tablecap(cs, out, valuetable)
|
||||
local n = 0;
|
||||
local t = {}
|
||||
cs.cap = cs.cap + 1
|
||||
-- table is empty
|
||||
if cs.ocap[cs.cap - 1].siz == 0 then
|
||||
while cs.ocap[cs.cap].kind ~= Cclose do
|
||||
local subout = { outindex = 0, out = {} }
|
||||
-- named group?
|
||||
if cs.ocap[cs.cap].kind == Cgroup and cs.ocap[cs.cap].idx ~= 0 then
|
||||
local groupname = valuetable[cs.ocap[cs.cap].idx] -- push group name
|
||||
pushonenestedvalue(cs, subout, valuetable)
|
||||
t[groupname] = subout.out[1]
|
||||
else
|
||||
-- not a named group
|
||||
local k = pushcapture(cs, subout, valuetable)
|
||||
-- store all values into table
|
||||
for i = 1, subout.outindex do
|
||||
t[i + n] = subout.out[i]
|
||||
end
|
||||
n = n + k;
|
||||
end
|
||||
end
|
||||
cs.cap = cs.cap + 1 -- skip close entry
|
||||
end
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = t
|
||||
return 1; -- number of values pushed (only the table)
|
||||
end
|
||||
|
||||
|
||||
-- Table-query capture
|
||||
|
||||
local function querycap(cs, out, valuetable)
|
||||
local table = valuetable[cs.ocap[cs.cap].idx]
|
||||
local subout = { outindex = 0, out = {} }
|
||||
pushonenestedvalue(cs, subout, valuetable) -- get nested capture
|
||||
-- query cap. value at table
|
||||
if table[subout.out[1]] ~= nil then
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = table[subout.out[1]]
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
-- Fold capture
|
||||
|
||||
local function foldcap(cs, out, valuetable)
|
||||
local fce = valuetable[cs.ocap[cs.cap].idx]
|
||||
cs.cap = cs.cap + 1
|
||||
-- no nested captures?
|
||||
-- or no nested captures (large subject)?
|
||||
if cs.ocap[cs.cap - 1].siz ~= 0 or
|
||||
cs.ocap[cs.cap].kind == Cclose then
|
||||
error("no initial value for fold capture", 0);
|
||||
end
|
||||
local subout = { outindex = 0; out = {} }
|
||||
local n = pushcapture(cs, subout, valuetable) -- nested captures with no values?
|
||||
if n == 0 then
|
||||
error("no initial value for fold capture", 0);
|
||||
end
|
||||
local acumulator = subout.out[1] -- leave only one result for accumulator
|
||||
while cs.ocap[cs.cap].kind ~= Cclose do
|
||||
local subout = { outindex = 0; out = {} }
|
||||
n = pushcapture(cs, subout, valuetable); -- get next capture's values
|
||||
acumulator = fce(acumulator, unpack(subout.out, 1, subout.outindex)) -- call folding function
|
||||
end
|
||||
cs.cap = cs.cap + 1; -- skip close entry
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = acumulator
|
||||
return 1; -- only accumulator left on the stack
|
||||
end
|
||||
|
||||
|
||||
local function retcount(...)
|
||||
return select('#', ...), { ... }
|
||||
end
|
||||
|
||||
|
||||
-- Function capture
|
||||
|
||||
local function functioncap(cs, out, valuetable)
|
||||
local fce = valuetable[cs.ocap[cs.cap].idx] -- push function
|
||||
local subout = { outindex = 0, out = {} }
|
||||
local n = pushnestedvalues(cs, false, subout, valuetable); -- push nested captures
|
||||
local count, ret = retcount(fce(unpack(subout.out, 1, n))) -- call function
|
||||
for i = 1, count do
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = ret[i]
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
|
||||
-- Select capture
|
||||
|
||||
local function numcap(cs, out, valuetable)
|
||||
local idx = valuetable[cs.ocap[cs.cap].idx] -- value to select
|
||||
-- no values?
|
||||
if idx == 0 then
|
||||
nextcap(cs); -- skip entire capture
|
||||
return 0; -- no value produced
|
||||
else
|
||||
local subout = { outindex = 0, out = {} }
|
||||
local n = pushnestedvalues(cs, false, subout, valuetable)
|
||||
-- invalid index?
|
||||
if n < idx then
|
||||
error(("no capture '%d'"):format(idx), 0)
|
||||
else
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = subout.out[idx] -- get selected capture
|
||||
return 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Calls a runtime capture. Returns number of captures removed by
|
||||
-- the call, including the initial Cgroup. (Captures to be added are
|
||||
-- on the Lua stack.)
|
||||
|
||||
local function runtimecap(cs, close, s, out, valuetable)
|
||||
local open = findopen(cs, close)
|
||||
assert(cs.ocap[open].kind == Cgroup)
|
||||
cs.ocap[close].kind = Cclose; -- closes the group
|
||||
cs.ocap[close].s = s;
|
||||
cs.cap = open;
|
||||
local fce = valuetable[cs.ocap[cs.cap].idx] -- push function to be called
|
||||
local subout = { outindex = 0, out = {} }
|
||||
local n = pushnestedvalues(cs, false, subout, valuetable); -- push nested captures
|
||||
local count, ret = retcount(fce(cs.s or cs.stream, s, unpack(subout.out, 1, n))) -- call dynamic function
|
||||
for i = 1, count do
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = ret[i]
|
||||
end
|
||||
return close - open -- number of captures of all kinds removed
|
||||
end
|
||||
|
||||
-- Collect values from current capture into array 'cps'. Current
|
||||
-- capture must be Cstring (first call) or Csimple (recursive calls).
|
||||
-- (In first call, fills %0 with whole match for Cstring.)
|
||||
-- Returns number of elements in the array that were filled.
|
||||
|
||||
local function getstrcaps(cs, cps, n)
|
||||
local k = n
|
||||
n = n + 1
|
||||
cps[k + 1].isstring = true; -- get string value
|
||||
cps[k + 1].startstr = cs.ocap[cs.cap].s; -- starts here
|
||||
cs.cap = cs.cap + 1
|
||||
-- nested captures?
|
||||
if cs.ocap[cs.cap - 1].siz == 0 then
|
||||
-- traverse them
|
||||
while cs.ocap[cs.cap].kind ~= Cclose do
|
||||
-- too many captures?
|
||||
if n >= MAXSTRCAPS then
|
||||
nextcap(cs); -- skip extra captures (will not need them)
|
||||
elseif cs.ocap[cs.cap].kind == Csimple then
|
||||
-- string?
|
||||
n = getstrcaps(cs, cps, n); -- put info. into array
|
||||
else
|
||||
cps[n + 1].isstring = false; -- not a string
|
||||
cps[n + 1].origcap = cs.cap; -- keep original capture
|
||||
nextcap(cs);
|
||||
n = n + 1;
|
||||
end
|
||||
end
|
||||
cs.cap = cs.cap + 1 -- skip close
|
||||
end
|
||||
cps[k + 1].endstr = cs.ocap[cs.cap - 1].s + cs.ocap[cs.cap - 1].siz - 1 -- ends here
|
||||
return n;
|
||||
end
|
||||
|
||||
|
||||
-- add next capture value (which should be a string) to buffer 'b'
|
||||
|
||||
-- String capture: add result to buffer 'b' (instead of pushing
|
||||
-- it into the stack)
|
||||
|
||||
local function stringcap(cs, b, valuetable)
|
||||
local cps = {}
|
||||
for i = 1, MAXSTRCAPS do
|
||||
cps[#cps + 1] = {}
|
||||
end
|
||||
local fmt = valuetable[cs.ocap[cs.cap].idx]
|
||||
local n = getstrcaps(cs, cps, 0) - 1; -- collect nested captures
|
||||
local i = 1
|
||||
-- traverse them
|
||||
while i <= #fmt do
|
||||
local c = fmt:sub(i, i)
|
||||
-- not an escape?
|
||||
if c ~= '%' then
|
||||
b[#b + 1] = c -- add it to buffer
|
||||
elseif fmt:sub(i + 1, i + 1) < '0' or fmt:sub(i + 1, i + 1) > '9' then
|
||||
-- not followed by a digit?
|
||||
i = i + 1
|
||||
b[#b + 1] = fmt:sub(i, i)
|
||||
else
|
||||
i = i + 1
|
||||
local l = fmt:sub(i, i) - '0'; -- capture index
|
||||
if l > n then
|
||||
error(("invalid capture index (%d)"):format(l), 0)
|
||||
elseif cps[l + 1].isstring then
|
||||
b[#b + 1] = cs.s and cs.s:sub(cps[l + 1].startstr, cps[l + 1].endstr - cps[l + 1].startstr + cps[l + 1].startstr - 1) or
|
||||
cs.stream(cps[l + 1].startstr, cps[l + 1].endstr - cps[l + 1].startstr + cps[l + 1].startstr - 1)
|
||||
else
|
||||
local curr = cs.cap;
|
||||
cs.cap = cps[l + 1].origcap; -- go back to evaluate that nested capture
|
||||
if not addonestring(cs, b, "capture", valuetable) then
|
||||
error(("no values in capture index %d"):format(l), 0)
|
||||
end
|
||||
cs.cap = curr; -- continue from where it stopped
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Substitution capture: add result to buffer 'b'
|
||||
|
||||
local function substcap(cs, b, valuetable)
|
||||
local curr = cs.ocap[cs.cap].s;
|
||||
-- no nested captures?
|
||||
if cs.ocap[cs.cap].siz ~= 0 then
|
||||
-- keep original text
|
||||
b[#b + 1] = cs.s and cs.s:sub(curr, cs.ocap[cs.cap].siz - 1 + curr - 1) or
|
||||
cs.stream(curr, cs.ocap[cs.cap].siz - 1 + curr - 1)
|
||||
else
|
||||
cs.cap = cs.cap + 1 -- skip open entry
|
||||
-- traverse nested captures
|
||||
while cs.ocap[cs.cap].kind ~= Cclose do
|
||||
local next = cs.ocap[cs.cap].s;
|
||||
b[#b + 1] = cs.s and cs.s:sub(curr, next - curr + curr - 1) or
|
||||
cs.stream(curr, next - curr + curr - 1) -- add text up to capture
|
||||
if addonestring(cs, b, "replacement", valuetable) then
|
||||
curr = cs.ocap[cs.cap - 1].s + cs.ocap[cs.cap - 1].siz - 1; -- continue after match
|
||||
else
|
||||
-- no capture value
|
||||
curr = next; -- keep original text in final result
|
||||
end
|
||||
end
|
||||
b[#b + 1] = cs.s and cs.s:sub(curr, curr + cs.ocap[cs.cap].s - curr - 1) or
|
||||
cs.stream(curr, curr + cs.ocap[cs.cap].s - curr - 1) -- add last piece of text
|
||||
end
|
||||
cs.cap = cs.cap + 1 -- go to next capture
|
||||
end
|
||||
|
||||
|
||||
-- Evaluates a capture and adds its first value to buffer 'b'; returns
|
||||
-- whether there was a value
|
||||
|
||||
function addonestring(cs, b, what, valuetable)
|
||||
local tag = cs.ocap[cs.cap].kind
|
||||
if tag == Cstring then
|
||||
stringcap(cs, b, valuetable); -- add capture directly to buffer
|
||||
return 1
|
||||
elseif tag == Csubst then
|
||||
substcap(cs, b, valuetable); -- add capture directly to buffer
|
||||
return 1
|
||||
else
|
||||
local subout = { outindex = 0, out = {} }
|
||||
local n = pushcapture(cs, subout, valuetable);
|
||||
if n > 0 then
|
||||
if type(subout.out[1]) ~= 'string' and type(subout.out[1]) ~= 'number' then
|
||||
error(("invalid %s value (a %s)"):format(what, type(subout.out[1])), 0)
|
||||
end
|
||||
b[#b + 1] = subout.out[1]
|
||||
return n
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Push all values of the current capture into the stack; returns
|
||||
-- number of values pushed
|
||||
|
||||
function pushcapture(cs, out, valuetable)
|
||||
local type = cs.ocap[cs.cap].kind
|
||||
if type == Cposition then
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = cs.ocap[cs.cap].s
|
||||
cs.cap = cs.cap + 1;
|
||||
return 1;
|
||||
elseif type == Cconst then
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = valuetable[cs.ocap[cs.cap].idx]
|
||||
cs.cap = cs.cap + 1
|
||||
return 1;
|
||||
elseif type == Carg then
|
||||
local arg = valuetable[cs.ocap[cs.cap].idx]
|
||||
cs.cap = cs.cap + 1
|
||||
if arg > cs.ptopcount then
|
||||
error(("reference to absent extra argument #%d"):format(arg), 0)
|
||||
end
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = cs.ptop[arg]
|
||||
return 1;
|
||||
elseif type == Csimple then
|
||||
local k = pushnestedvalues(cs, true, out, valuetable)
|
||||
local index = out.outindex
|
||||
table.insert(out.out, index - k + 1, out.out[index])
|
||||
out[index + 1] = nil
|
||||
return k;
|
||||
elseif type == Cruntime then
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = valuetable[cs.ocap[cs.cap].idx]
|
||||
cs.cap = cs.cap + 1;
|
||||
return 1;
|
||||
elseif type == Cstring then
|
||||
local b = {}
|
||||
stringcap(cs, b, valuetable)
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = table.concat(b)
|
||||
return 1;
|
||||
elseif type == Csubst then
|
||||
local b = {}
|
||||
substcap(cs, b, valuetable);
|
||||
out.outindex = out.outindex + 1
|
||||
out.out[out.outindex] = table.concat(b)
|
||||
return 1;
|
||||
elseif type == Cgroup then
|
||||
-- anonymous group?
|
||||
if cs.ocap[cs.cap].idx == 0 then
|
||||
return pushnestedvalues(cs, false, out, valuetable); -- add all nested values
|
||||
else
|
||||
-- named group: add no values
|
||||
nextcap(cs); -- skip capture
|
||||
return 0
|
||||
end
|
||||
elseif type == Cbackref then
|
||||
return backrefcap(cs, out, valuetable)
|
||||
elseif type == Ctable then
|
||||
return tablecap(cs, out, valuetable)
|
||||
elseif type == Cfunction then
|
||||
return functioncap(cs, out, valuetable)
|
||||
elseif type == Cnum then
|
||||
return numcap(cs, out, valuetable)
|
||||
elseif type == Cquery then
|
||||
return querycap(cs, out, valuetable)
|
||||
elseif type == Cfold then
|
||||
return foldcap(cs, out, valuetable)
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Prepare a CapState structure and traverse the entire list of
|
||||
-- captures in the stack pushing its results. 's' is the subject
|
||||
-- string, 'r' is the final position of the match, and 'ptop'
|
||||
-- the index in the stack where some useful values were pushed.
|
||||
-- Returns the number of results pushed. (If the list produces no
|
||||
-- results, push the final position of the match.)
|
||||
|
||||
local function getcaptures(capture, s, stream, r, valuetable, ...)
|
||||
local n = 0;
|
||||
local cs = { cap = 0 }
|
||||
local out = { outindex = 0; out = {} }
|
||||
-- is there any capture?
|
||||
if capture[cs.cap].kind ~= Cclose then
|
||||
cs.ocap = capture
|
||||
cs.s = s;
|
||||
cs.stream = stream
|
||||
cs.ptopcount, cs.ptop = retcount(...)
|
||||
repeat -- collect their values
|
||||
n = n + pushcapture(cs, out, valuetable)
|
||||
until cs.ocap[cs.cap].kind == Cclose
|
||||
end
|
||||
-- no capture values?
|
||||
if n == 0 then
|
||||
if not r then
|
||||
return
|
||||
else
|
||||
return r
|
||||
end
|
||||
end
|
||||
assert(out.outindex < 7998, "(too many captures)")
|
||||
return unpack(out.out, 1, out.outindex)
|
||||
end
|
||||
|
||||
local function getcapturesruntime(capture, s, stream, notdelete, min, max, captop, valuetable, ...)
|
||||
local n = 0;
|
||||
local cs = { cap = min }
|
||||
local out = { outindex = 0; out = {} }
|
||||
cs.ocap = capture
|
||||
cs.s = s
|
||||
cs.stream = stream
|
||||
cs.ptopcount, cs.ptop = retcount(...)
|
||||
local start = 0
|
||||
repeat -- collect their values
|
||||
if not checknextcap(cs, max) then break end
|
||||
local notdelete = notdelete or capture[cs.cap].kind == Cgroup and capture[cs.cap].idx ~= 0 and capture[cs.cap].candelete == 0
|
||||
pushcapture(cs, out, valuetable)
|
||||
if notdelete then
|
||||
start = cs.cap
|
||||
else
|
||||
n = n + cs.cap - start
|
||||
for i = 0, captop - cs.cap - 1 do
|
||||
ffi.copy(capture + start + i, capture + cs.cap + i, ffi.sizeof('CAPTURE'))
|
||||
end
|
||||
max = max - (cs.cap - start)
|
||||
captop = captop - (cs.cap - start)
|
||||
cs.cap = start
|
||||
end
|
||||
until cs.cap == max
|
||||
assert(out.outindex < 7998, "(too many captures)")
|
||||
return n, out.out, out.outindex
|
||||
end
|
||||
|
||||
return {
|
||||
getcaptures = getcaptures,
|
||||
runtimecap = runtimecap,
|
||||
getcapturesruntime = getcapturesruntime,
|
||||
}
|
||||
|
1057
tools/lpeg/lpcode.lua
Normal file
1057
tools/lpeg/lpcode.lua
Normal file
File diff suppressed because it is too large
Load Diff
1373
tools/lpeg/lpeg.lua
Normal file
1373
tools/lpeg/lpeg.lua
Normal file
File diff suppressed because it is too large
Load Diff
356
tools/lpeg/lpprint.lua
Normal file
356
tools/lpeg/lpprint.lua
Normal file
@ -0,0 +1,356 @@
|
||||
--[[
|
||||
LPEGLJ
|
||||
lpprint.lua
|
||||
Tree, code and debug print function (only for debuging)
|
||||
Copyright (C) 2014 Rostislav Sacek.
|
||||
based on LPeg v1.0 - PEG pattern matching for Lua
|
||||
Lua.org & PUC-Rio written by Roberto Ierusalimschy
|
||||
http://www.inf.puc-rio.br/~roberto/lpeg/
|
||||
|
||||
** 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.
|
||||
**
|
||||
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
|
||||
--]]
|
||||
|
||||
local ffi = require"ffi"
|
||||
local band, rshift, lshift = bit.band, bit.rshift, bit.lshift
|
||||
|
||||
ffi.cdef[[
|
||||
int isprint ( int c );
|
||||
]]
|
||||
|
||||
local RuleLR = 0x10000
|
||||
local Ruleused = 0x20000
|
||||
|
||||
-- {======================================================
|
||||
-- Printing patterns (for debugging)
|
||||
-- =======================================================
|
||||
|
||||
local TChar = 0
|
||||
local TSet = 1
|
||||
local TAny = 2 -- standard PEG elements
|
||||
local TTrue = 3
|
||||
local TFalse = 4
|
||||
local TRep = 5
|
||||
local TSeq = 6
|
||||
local TChoice = 7
|
||||
local TNot = 8
|
||||
local TAnd = 9
|
||||
local TCall = 10
|
||||
local TOpenCall = 11
|
||||
local TRule = 12 -- sib1 is rule's pattern, sib2 is 'next' rule
|
||||
local TGrammar = 13 -- sib1 is initial (and first) rule
|
||||
local TBehind = 14 -- match behind
|
||||
local TCapture = 15 -- regular capture
|
||||
local TRunTime = 16 -- run-time capture
|
||||
|
||||
local IAny = 0 -- if no char, fail
|
||||
local IChar = 1 -- if char != aux, fail
|
||||
local ISet = 2 -- if char not in val, fail
|
||||
local ITestAny = 3 -- in no char, jump to 'offset'
|
||||
local ITestChar = 4 -- if char != aux, jump to 'offset'
|
||||
local ITestSet = 5 -- if char not in val, jump to 'offset'
|
||||
local ISpan = 6 -- read a span of chars in val
|
||||
local IBehind = 7 -- walk back 'aux' characters (fail if not possible)
|
||||
local IRet = 8 -- return from a rule
|
||||
local IEnd = 9 -- end of pattern
|
||||
local IChoice = 10 -- stack a choice; next fail will jump to 'offset'
|
||||
local IJmp = 11 -- jump to 'offset'
|
||||
local ICall = 12 -- call rule at 'offset'
|
||||
local IOpenCall = 13 -- call rule number 'offset' (must be closed to a ICall)
|
||||
local ICommit = 14 -- pop choice and jump to 'offset'
|
||||
local IPartialCommit = 15 -- update top choice to current position and jump
|
||||
local IBackCommit = 16 -- "fails" but jump to its own 'offset'
|
||||
local IFailTwice = 17 -- pop one choice and then fail
|
||||
local IFail = 18 -- go back to saved state on choice and jump to saved offset
|
||||
local IGiveup = 19 -- internal use
|
||||
local IFullCapture = 20 -- complete capture of last 'off' chars
|
||||
local IOpenCapture = 21 -- start a capture
|
||||
local ICloseCapture = 22
|
||||
local ICloseRunTime = 23
|
||||
|
||||
local Cclose = 0
|
||||
local Cposition = 1
|
||||
local Cconst = 2
|
||||
local Cbackref = 3
|
||||
local Carg = 4
|
||||
local Csimple = 5
|
||||
local Ctable = 6
|
||||
local Cfunction = 7
|
||||
local Cquery = 8
|
||||
local Cstring = 9
|
||||
local Cnum = 10
|
||||
local Csubst = 11
|
||||
local Cfold = 12
|
||||
local Cruntime = 13
|
||||
local Cgroup = 14
|
||||
|
||||
|
||||
-- number of siblings for each tree
|
||||
local numsiblings = {
|
||||
[TRep] = 1,
|
||||
[TSeq] = 2,
|
||||
[TChoice] = 2,
|
||||
[TNot] = 1,
|
||||
[TAnd] = 1,
|
||||
[TRule] = 2,
|
||||
[TGrammar] = 1,
|
||||
[TBehind] = 1,
|
||||
[TCapture] = 1,
|
||||
[TRunTime] = 1,
|
||||
}
|
||||
local names = {
|
||||
[IAny] = "any",
|
||||
[IChar] = "char",
|
||||
[ISet] = "set",
|
||||
[ITestAny] = "testany",
|
||||
[ITestChar] = "testchar",
|
||||
[ITestSet] = "testset",
|
||||
[ISpan] = "span",
|
||||
[IBehind] = "behind",
|
||||
[IRet] = "ret",
|
||||
[IEnd] = "end",
|
||||
[IChoice] = "choice",
|
||||
[IJmp] = "jmp",
|
||||
[ICall] = "call",
|
||||
[IOpenCall] = "open_call",
|
||||
[ICommit] = "commit",
|
||||
[IPartialCommit] = "partial_commit",
|
||||
[IBackCommit] = "back_commit",
|
||||
[IFailTwice] = "failtwice",
|
||||
[IFail] = "fail",
|
||||
[IGiveup] = "giveup",
|
||||
[IFullCapture] = "fullcapture",
|
||||
[IOpenCapture] = "opencapture",
|
||||
[ICloseCapture] = "closecapture",
|
||||
[ICloseRunTime] = "closeruntime"
|
||||
}
|
||||
|
||||
local function printcharset(st)
|
||||
io.write("[");
|
||||
local i = 0
|
||||
while i <= 255 do
|
||||
local first = i;
|
||||
while band(st[rshift(i, 5)], lshift(1, band(i, 31))) ~= 0 and i <= 255 do
|
||||
i = i + 1
|
||||
end
|
||||
if i - 1 == first then -- unary range?
|
||||
io.write(("(%02x)"):format(first))
|
||||
elseif i - 1 > first then -- non-empty range?
|
||||
io.write(("(%02x-%02x)"):format(first, i - 1))
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
io.write("]")
|
||||
end
|
||||
|
||||
local modes = {
|
||||
[Cclose] = "close",
|
||||
[Cposition] = "position",
|
||||
[Cconst] = "constant",
|
||||
[Cbackref] = "backref",
|
||||
[Carg] = "argument",
|
||||
[Csimple] = "simple",
|
||||
[Ctable] = "table",
|
||||
[Cfunction] = "function",
|
||||
[Cquery] = "query",
|
||||
[Cstring] = "string",
|
||||
[Cnum] = "num",
|
||||
[Csubst] = "substitution",
|
||||
[Cfold] = "fold",
|
||||
[Cruntime] = "runtime",
|
||||
[Cgroup] = "group"
|
||||
}
|
||||
|
||||
local function printcapkind(kind)
|
||||
io.write(("%s"):format(modes[kind]))
|
||||
end
|
||||
|
||||
local function printjmp(p, index)
|
||||
io.write(("-> %d"):format(index + p[index].offset))
|
||||
end
|
||||
|
||||
local function printrulename(p, index, rulenames)
|
||||
if rulenames and rulenames[index + p[index].offset] then
|
||||
io.write(' ', rulenames[index + p[index].offset])
|
||||
end
|
||||
end
|
||||
|
||||
local function printinst(p, index, valuetable, rulenames)
|
||||
local code = p[index].code
|
||||
if rulenames and rulenames[index] then
|
||||
io.write(rulenames[index], '\n')
|
||||
end
|
||||
io.write(("%04d: %s "):format(index, names[code]))
|
||||
if code == IChar then
|
||||
io.write(("'%s'"):format(string.char(p[index].val)))
|
||||
elseif code == ITestChar then
|
||||
io.write(("'%s'"):format(string.char(p[index].val)))
|
||||
printjmp(p, index)
|
||||
printrulename(p, index, rulenames)
|
||||
elseif code == IFullCapture then
|
||||
printcapkind(band(p[index].val, 0x0f));
|
||||
io.write((" (size = %d) (idx = %s)"):format(band(rshift(p[index].val, 4), 0xF), tostring(valuetable[p[index].offset])))
|
||||
elseif code == IOpenCapture then
|
||||
printcapkind(band(p[index].val, 0x0f))
|
||||
io.write((" (idx = %s)"):format(tostring(valuetable[p[index].offset])))
|
||||
elseif code == ISet then
|
||||
printcharset(valuetable[p[index].val]);
|
||||
elseif code == ITestSet then
|
||||
printcharset(valuetable[p[index].val])
|
||||
printjmp(p, index);
|
||||
printrulename(p, index, rulenames)
|
||||
elseif code == ISpan then
|
||||
printcharset(valuetable[p[index].val]);
|
||||
elseif code == IOpenCall then
|
||||
io.write(("-> %d"):format(p[index].offset))
|
||||
elseif code == IBehind then
|
||||
io.write(("%d"):format(p[index].val))
|
||||
elseif code == IJmp or code == ICall or code == ICommit or code == IChoice or
|
||||
code == IPartialCommit or code == IBackCommit or code == ITestAny then
|
||||
printjmp(p, index);
|
||||
if (code == ICall or code == IJmp) and p[index].aux > 0 then
|
||||
io.write(' ', valuetable[p[index].aux])
|
||||
else
|
||||
printrulename(p, index, rulenames)
|
||||
end
|
||||
end
|
||||
io.write("\n")
|
||||
end
|
||||
|
||||
|
||||
local function printpatt(p, valuetable)
|
||||
local ruleNames = {}
|
||||
for i = 0, p.size - 1 do
|
||||
local code = p.p[i].code
|
||||
if (code == ICall or code == IJmp) and p.p[i].aux > 0 then
|
||||
local index = i + p.p[i].offset
|
||||
ruleNames[index] = valuetable[p.p[i].aux]
|
||||
end
|
||||
end
|
||||
for i = 0, p.size - 1 do
|
||||
printinst(p.p, i, valuetable, ruleNames)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function printcap(cap, index, valuetable)
|
||||
printcapkind(cap[index].kind)
|
||||
io.write((" (idx: %s - size: %d) -> %d\n"):format(valuetable[cap[index].idx], cap[index].siz, cap[index].s))
|
||||
end
|
||||
|
||||
|
||||
local function printcaplist(cap, limit, valuetable)
|
||||
io.write(">======\n")
|
||||
local index = 0
|
||||
while cap[index].s and index < limit do
|
||||
printcap(cap, index, valuetable)
|
||||
index = index + 1
|
||||
end
|
||||
io.write("=======\n")
|
||||
end
|
||||
|
||||
-- ======================================================
|
||||
|
||||
|
||||
|
||||
-- {======================================================
|
||||
-- Printing trees (for debugging)
|
||||
-- =======================================================
|
||||
|
||||
local tagnames = {
|
||||
[TChar] = "char",
|
||||
[TSet] = "set",
|
||||
[TAny] = "any",
|
||||
[TTrue] = "true",
|
||||
[TFalse] = "false",
|
||||
[TRep] = "rep",
|
||||
[TSeq] = "seq",
|
||||
[TChoice] = "choice",
|
||||
[TNot] = "not",
|
||||
[TAnd] = "and",
|
||||
[TCall] = "call",
|
||||
[TOpenCall] = "opencall",
|
||||
[TRule] = "rule",
|
||||
[TGrammar] = "grammar",
|
||||
[TBehind] = "behind",
|
||||
[TCapture] = "capture",
|
||||
[TRunTime] = "run-time"
|
||||
}
|
||||
|
||||
|
||||
local function printtree(tree, ident, index, valuetable)
|
||||
for i = 1, ident do
|
||||
io.write(" ")
|
||||
end
|
||||
local tag = tree[index].tag
|
||||
io.write(("%s"):format(tagnames[tag]))
|
||||
if tag == TChar then
|
||||
local c = tree[index].val
|
||||
if ffi.C.isprint(c) then
|
||||
io.write((" '%c'\n"):format(c))
|
||||
else
|
||||
io.write((" (%02X)\n"):format(c))
|
||||
end
|
||||
elseif tag == TSet then
|
||||
printcharset(valuetable[tree[index].val]);
|
||||
io.write("\n")
|
||||
elseif tag == TOpenCall or tag == TCall then
|
||||
io.write((" key: %s\n"):format(tostring(valuetable[tree[index].val])))
|
||||
elseif tag == TBehind then
|
||||
io.write((" %d\n"):format(tree[index].val))
|
||||
printtree(tree, ident + 2, index + 1, valuetable);
|
||||
elseif tag == TCapture then
|
||||
io.write((" cap: %s n: %s\n"):format(modes[bit.band(tree[index].cap, 0xffff)], valuetable[tree[index].val]))
|
||||
printtree(tree, ident + 2, index + 1, valuetable);
|
||||
elseif tag == TRule then
|
||||
local extra = bit.band(tree[index].cap, RuleLR) == RuleLR and ' left recursive' or ''
|
||||
extra = extra .. (bit.band(tree[index].cap, Ruleused) ~= Ruleused and ' not used' or '')
|
||||
io.write((" n: %d key: %s%s\n"):format(bit.band(tree[index].cap, 0xffff) - 1, valuetable[tree[index].val], extra))
|
||||
printtree(tree, ident + 2, index + 1, valuetable);
|
||||
-- do not print next rule as a sibling
|
||||
elseif tag == TGrammar then
|
||||
local ruleindex = index + 1
|
||||
io.write((" %d\n"):format(tree[index].val)) -- number of rules
|
||||
for i = 1, tree[index].val do
|
||||
printtree(tree, ident + 2, ruleindex, valuetable);
|
||||
ruleindex = ruleindex + tree[ruleindex].ps
|
||||
end
|
||||
assert(tree[ruleindex].tag == TTrue); -- sentinel
|
||||
else
|
||||
local sibs = numsiblings[tree[index].tag] or 0
|
||||
io.write("\n")
|
||||
if sibs >= 1 then
|
||||
printtree(tree, ident + 2, index + 1, valuetable);
|
||||
if sibs >= 2 then
|
||||
printtree(tree, ident + 2, index + tree[index].ps, valuetable)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- }====================================================== */
|
||||
|
||||
return {
|
||||
printtree = printtree,
|
||||
printpatt = printpatt,
|
||||
printcaplist = printcaplist,
|
||||
printinst = printinst
|
||||
}
|
1041
tools/lpeg/lpvm.lua
Normal file
1041
tools/lpeg/lpvm.lua
Normal file
File diff suppressed because it is too large
Load Diff
286
tools/lpeg/re.lua
Normal file
286
tools/lpeg/re.lua
Normal file
@ -0,0 +1,286 @@
|
||||
-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $
|
||||
-- 2014/08/15 changes rostislav
|
||||
|
||||
-- imported functions and modules
|
||||
local tonumber, print, error = tonumber, print, error
|
||||
local setmetatable = setmetatable
|
||||
local m = require"lpeglj"
|
||||
|
||||
-- 'm' will be used to parse expressions, and 'mm' will be used to
|
||||
-- create expressions; that is, 're' runs on 'm', creating patterns
|
||||
-- on 'mm'
|
||||
local mm = m
|
||||
|
||||
-- pattern's metatable
|
||||
local mt = getmetatable(mm.P(0))
|
||||
mt = m.version() == "1.0.0.0LJ" and m or mt
|
||||
|
||||
|
||||
|
||||
-- No more global accesses after this point
|
||||
local version = _VERSION
|
||||
if version == "Lua 5.2" then _ENV = nil end
|
||||
|
||||
|
||||
local any = m.P(1)
|
||||
|
||||
|
||||
-- Pre-defined names
|
||||
local Predef = { nl = m.P"\n" }
|
||||
|
||||
|
||||
local mem
|
||||
local fmem
|
||||
local gmem
|
||||
|
||||
|
||||
local function updatelocale ()
|
||||
mm.locale(Predef)
|
||||
Predef.a = Predef.alpha
|
||||
Predef.c = Predef.cntrl
|
||||
Predef.d = Predef.digit
|
||||
Predef.g = Predef.graph
|
||||
Predef.l = Predef.lower
|
||||
Predef.p = Predef.punct
|
||||
Predef.s = Predef.space
|
||||
Predef.u = Predef.upper
|
||||
Predef.w = Predef.alnum
|
||||
Predef.x = Predef.xdigit
|
||||
Predef.A = any - Predef.a
|
||||
Predef.C = any - Predef.c
|
||||
Predef.D = any - Predef.d
|
||||
Predef.G = any - Predef.g
|
||||
Predef.L = any - Predef.l
|
||||
Predef.P = any - Predef.p
|
||||
Predef.S = any - Predef.s
|
||||
Predef.U = any - Predef.u
|
||||
Predef.W = any - Predef.w
|
||||
Predef.X = any - Predef.x
|
||||
mem = {} -- restart memoization
|
||||
fmem = {}
|
||||
gmem = {}
|
||||
local mt = {__mode = "v"}
|
||||
setmetatable(mem, mt)
|
||||
setmetatable(fmem, mt)
|
||||
setmetatable(gmem, mt)
|
||||
end
|
||||
|
||||
|
||||
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) .. "..."
|
||||
msg = ("pattern error near '%s'"):format(msg)
|
||||
error(msg, 2)
|
||||
end
|
||||
|
||||
local function mult (p, n)
|
||||
local np = mm.P(true)
|
||||
while n >= 1 do
|
||||
if n%2 >= 1 then np = np * p end
|
||||
p = p * p
|
||||
n = n/2
|
||||
end
|
||||
return np
|
||||
end
|
||||
|
||||
local function equalcap (s, i, c)
|
||||
if type(c) ~= "string" then return nil end
|
||||
local e = #c + i
|
||||
if type(s) == 'function' then -- stream mode
|
||||
if s(i, e - 1) == c then return e else return nil end
|
||||
else
|
||||
if s:sub(i, e - 1) == c then return e else return nil end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
|
||||
|
||||
local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0
|
||||
|
||||
local arrow = S * "<-"
|
||||
|
||||
local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1
|
||||
|
||||
name = m.C(name)
|
||||
|
||||
|
||||
-- a defined name only have meaning in a given environment
|
||||
local Def = name * m.Carg(1)
|
||||
|
||||
local num = m.C(m.R"09"^1) * S / tonumber
|
||||
|
||||
local String = "'" * m.C((any - "'")^0) * "'" +
|
||||
'"' * m.C((any - '"')^0) * '"'
|
||||
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
|
||||
|
||||
local item = defined + Range + m.C(any)
|
||||
|
||||
local Class =
|
||||
"["
|
||||
* (m.C(m.P"^"^-1)) -- optional complement symbol
|
||||
* m.Cf(item * (item - "]")^0, mt.__add) /
|
||||
function (c, p) return c == "^" and any - p or p end
|
||||
* "]"
|
||||
|
||||
local function adddef (t, k, exp)
|
||||
if t[k] then
|
||||
error("'"..k.."' already defined as a rule")
|
||||
else
|
||||
t[k] = exp
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local function firstdef (n, r) return adddef({n}, n, r) end
|
||||
|
||||
|
||||
local function NT (n, b, p)
|
||||
if not b then
|
||||
error("rule '"..n.."' used outside a grammar")
|
||||
else return mm.V(n, p or 0)
|
||||
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)
|
||||
* (#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 *
|
||||
( ( 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 + num) * m.Cc(mt.__div))
|
||||
+ m.P"{}" * m.Cc(nil, m.Ct)
|
||||
+ m.Cg(Def / getdef * m.Cc(mt.__div))
|
||||
)
|
||||
+ "=>" * S * m.Cg(Def / getdef * m.Cc(m.Cmt))
|
||||
) * S
|
||||
)^0, function (a,b,f) return f(a,b) end );
|
||||
Primary = "(" * m.V"Exp" * ")"
|
||||
+ String / mm.P
|
||||
+ Class
|
||||
+ defined
|
||||
+ "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" /
|
||||
function (n, p) return mm.Cg(p, n) end
|
||||
+ "=" * 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 * m.Cb("G") * (S * ":" * S * num)^-1 * -arrow + "<" * name * m.Cb("G") * (S * ":" * S * num)^-1 * ">") / NT;
|
||||
Definition = name * arrow * m.V"Exp";
|
||||
Grammar = m.Cg(m.Cc(true), "G") *
|
||||
m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0,
|
||||
adddef) / mm.P
|
||||
}
|
||||
|
||||
local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
|
||||
|
||||
|
||||
local function compile (p, defs)
|
||||
if mm.type(p) == "pattern" then return p end -- already compiled
|
||||
local cp = pattern:match(p, 1, defs)
|
||||
if not cp then error("incorrect pattern", 3) end
|
||||
return cp
|
||||
end
|
||||
|
||||
local function match (s, p, i)
|
||||
local cp = mem[p]
|
||||
if not cp then
|
||||
cp = compile(p)
|
||||
mem[p] = cp
|
||||
end
|
||||
return cp:match(s, i or 1)
|
||||
end
|
||||
|
||||
local function streammatch (p, i)
|
||||
local cp = mem[p]
|
||||
if not cp then
|
||||
cp = compile(p)
|
||||
mem[p] = cp
|
||||
end
|
||||
return cp:streammatch(i or 1)
|
||||
end
|
||||
|
||||
-- Only for testing purpose
|
||||
local function emulatestreammatch(s, p, i)
|
||||
local cp = mem[p]
|
||||
if not cp then
|
||||
cp = compile(p)
|
||||
mem[p] = cp
|
||||
end
|
||||
return cp:emulatestreammatch(s, i or 1)
|
||||
end
|
||||
|
||||
local function find (s, p, i)
|
||||
local cp = fmem[p]
|
||||
if not cp then
|
||||
cp = compile(p) / 0
|
||||
cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
|
||||
fmem[p] = cp
|
||||
end
|
||||
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)
|
||||
local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
|
||||
gmem[p] = g
|
||||
local cp = g[rep]
|
||||
if not cp then
|
||||
cp = compile(p)
|
||||
cp = mm.Cs((cp / rep + 1)^0)
|
||||
g[rep] = cp
|
||||
end
|
||||
return cp:match(s)
|
||||
end
|
||||
|
||||
|
||||
-- exported names
|
||||
local re = {
|
||||
compile = compile,
|
||||
match = match,
|
||||
streammatch = streammatch,
|
||||
emulatestreammatch = emulatestreammatch,
|
||||
find = find,
|
||||
gsub = gsub,
|
||||
updatelocale = updatelocale,
|
||||
}
|
||||
|
||||
if version == "Lua 5.1" then _G.re = re end
|
||||
|
||||
return re
|
25
tools/luajson/json.lua
Normal file
25
tools/luajson/json.lua
Normal file
@ -0,0 +1,25 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
package.path = package.path .. ";lpeg/?.lua"
|
||||
local decode = require("json.decode")
|
||||
local encode = require("json.encode")
|
||||
local util = require("json.util")
|
||||
|
||||
local _G = _G
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local json = {
|
||||
_VERSION = "1.3.4",
|
||||
_DESCRIPTION = "LuaJSON : customizable JSON decoder/encoder",
|
||||
_COPYRIGHT = "Copyright (c) 2007-2014 Thomas Harning Jr. <harningt@gmail.com>",
|
||||
decode = decode,
|
||||
encode = encode,
|
||||
util = util
|
||||
}
|
||||
|
||||
_G.json = json
|
||||
|
||||
return json
|
171
tools/luajson/json/decode.lua
Normal file
171
tools/luajson/json/decode.lua
Normal file
@ -0,0 +1,171 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local lpeg = require("lpeg")
|
||||
|
||||
local error = error
|
||||
local pcall = pcall
|
||||
|
||||
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
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local modulesToLoad = {
|
||||
"composite",
|
||||
"strings",
|
||||
"number",
|
||||
"others"
|
||||
}
|
||||
local loadedModules = {
|
||||
}
|
||||
|
||||
local json_decode = {}
|
||||
|
||||
json_decode.default = {
|
||||
unicodeWhitespace = true,
|
||||
initialObject = false,
|
||||
nothrow = false
|
||||
}
|
||||
|
||||
local modes_defined = { "default", "strict", "simple" }
|
||||
|
||||
json_decode.simple = {}
|
||||
|
||||
json_decode.strict = {
|
||||
unicodeWhitespace = true,
|
||||
initialObject = true,
|
||||
nothrow = false
|
||||
}
|
||||
|
||||
for _,name in ipairs(modulesToLoad) do
|
||||
local mod = require("json.decode." .. name)
|
||||
if mod.mergeOptions then
|
||||
for _, mode in pairs(modes_defined) do
|
||||
mod.mergeOptions(json_decode[mode], mode)
|
||||
end
|
||||
end
|
||||
loadedModules[#loadedModules + 1] = mod
|
||||
end
|
||||
|
||||
-- Shift over default into defaultOptions to permit build optimization
|
||||
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 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(json_decode.default)
|
||||
local prebuilt_decoders = {}
|
||||
for _, mode in pairs(modes_defined) do
|
||||
if json_decode[mode] ~= nil then
|
||||
prebuilt_decoders[json_decode[mode]] = buildDecoder(json_decode[mode])
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Options:
|
||||
number => number decode options
|
||||
string => string decode options
|
||||
array => array decode options
|
||||
object => object decode options
|
||||
initialObject => whether or not to require the initial object to be a table/array
|
||||
allowUndefined => whether or not to allow undefined values
|
||||
]]
|
||||
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
|
||||
end
|
||||
return buildDecoder(mode)
|
||||
end
|
||||
|
||||
local function decode(data, mode)
|
||||
local decoder = getDecoder(mode)
|
||||
return decoder(data)
|
||||
end
|
||||
|
||||
local mt = {}
|
||||
mt.__call = function(self, ...)
|
||||
return decode(...)
|
||||
end
|
||||
|
||||
json_decode.getDecoder = getDecoder
|
||||
json_decode.decode = decode
|
||||
json_decode.util = util
|
||||
setmetatable(json_decode, mt)
|
||||
|
||||
return json_decode
|
190
tools/luajson/json/decode/composite.lua
Normal file
190
tools/luajson/json/decode/composite.lua
Normal file
@ -0,0 +1,190 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
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 = {
|
||||
trailingComma = true
|
||||
},
|
||||
object = {
|
||||
trailingComma = true,
|
||||
number = true,
|
||||
identifier = true,
|
||||
setObjectKey = rawset
|
||||
},
|
||||
calls = {
|
||||
defs = nil,
|
||||
-- By default, do not allow undefined calls to be de-serialized as call objects
|
||||
allowUndefined = false
|
||||
}
|
||||
}
|
||||
|
||||
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
|
100
tools/luajson/json/decode/number.lua
Normal file
100
tools/luajson/json/decode/number.lua
Normal file
@ -0,0 +1,100 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local lpeg = require("lpeg")
|
||||
local tonumber = tonumber
|
||||
local jsonutil = require("json.util")
|
||||
local merge = jsonutil.merge
|
||||
local util = require("json.decode.util")
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local digit = lpeg.R("09")
|
||||
local digits = digit^1
|
||||
|
||||
-- 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
|
||||
|
||||
local exp = lpeg.S("Ee") * (lpeg.S("-+") + 0) * digits
|
||||
|
||||
local nan = lpeg.S("Nn") * lpeg.S("Aa") * lpeg.S("Nn")
|
||||
local inf = lpeg.S("Ii") * lpeg.P("nfinity")
|
||||
local ninf = lpeg.P('-') * lpeg.S("Ii") * lpeg.P("nfinity")
|
||||
local hex = (lpeg.P("0x") + lpeg.P("0X")) * lpeg.R("09","AF","af")^1
|
||||
|
||||
local defaultOptions = {
|
||||
nan = true,
|
||||
inf = true,
|
||||
frac = true,
|
||||
exp = true,
|
||||
hex = false
|
||||
}
|
||||
|
||||
local modeOptions = {}
|
||||
|
||||
modeOptions.strict = {
|
||||
nan = false,
|
||||
inf = false
|
||||
}
|
||||
|
||||
local nan_value = 0/0
|
||||
local inf_value = 1/0
|
||||
local ninf_value = -1/0
|
||||
|
||||
--[[
|
||||
Options: configuration options for number rules
|
||||
nan: match NaN
|
||||
inf: match Infinity
|
||||
frac: match fraction portion (.0)
|
||||
exp: match exponent portion (e1)
|
||||
DEFAULT: nan, inf, frac, exp
|
||||
]]
|
||||
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
|
||||
|
||||
local number = {
|
||||
int = int,
|
||||
mergeOptions = mergeOptions,
|
||||
generateLexer = generateLexer
|
||||
}
|
||||
|
||||
return number
|
62
tools/luajson/json/decode/others.lua
Normal file
62
tools/luajson/json/decode/others.lua
Normal file
@ -0,0 +1,62 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local lpeg = require("lpeg")
|
||||
local jsonutil = require("json.util")
|
||||
local merge = jsonutil.merge
|
||||
local util = require("json.decode.util")
|
||||
|
||||
-- Container module for other JavaScript types (bool, null, undefined)
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
-- For null and undefined, use the util.null value to preserve null-ness
|
||||
local booleanCapture =
|
||||
lpeg.P("true") * lpeg.Cc(true)
|
||||
+ lpeg.P("false") * lpeg.Cc(false)
|
||||
|
||||
local nullCapture = lpeg.P("null")
|
||||
local undefinedCapture = lpeg.P("undefined")
|
||||
|
||||
local defaultOptions = {
|
||||
allowUndefined = true,
|
||||
null = jsonutil.null,
|
||||
undefined = jsonutil.undefined
|
||||
}
|
||||
|
||||
local modeOptions = {}
|
||||
|
||||
modeOptions.simple = {
|
||||
null = false, -- Mapped to nil
|
||||
undefined = false -- Mapped to nil
|
||||
}
|
||||
modeOptions.strict = {
|
||||
allowUndefined = false
|
||||
}
|
||||
|
||||
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.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
|
||||
|
||||
local others = {
|
||||
mergeOptions = mergeOptions,
|
||||
generateLexer = generateLexer
|
||||
}
|
||||
|
||||
return others
|
189
tools/luajson/json/decode/state.lua
Normal file
189
tools/luajson/json/decode/state.lua
Normal file
@ -0,0 +1,189 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
|
||||
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())
|
||||
self.active_key = nil
|
||||
end
|
||||
|
||||
function state_ops.put_array_value(self, trailing)
|
||||
-- Safety check
|
||||
if trailing and not self.previous_set and self.options.array.trailingComma then
|
||||
return
|
||||
end
|
||||
local new_index = self.active_state + 1
|
||||
self.active_state = new_index
|
||||
self.active[new_index] = self:grab_value()
|
||||
end
|
||||
|
||||
function state_ops.put_value(self, trailing)
|
||||
if self.active_state == 'object' then
|
||||
self:put_object_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.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)
|
||||
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 ret = {
|
||||
options = options,
|
||||
stack = {},
|
||||
state_stack = {},
|
||||
key_stack = {},
|
||||
i = 0,
|
||||
active = nil,
|
||||
active_key = nil,
|
||||
previous = nil,
|
||||
active_state = nil
|
||||
|
||||
}
|
||||
return setmetatable(ret, state_mt)
|
||||
end
|
||||
|
||||
local state = {
|
||||
create = create
|
||||
}
|
||||
|
||||
return state
|
133
tools/luajson/json/decode/strings.lua
Normal file
133
tools/luajson/json/decode/strings.lua
Normal file
@ -0,0 +1,133 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local lpeg = require("lpeg")
|
||||
local jsonutil = require("json.util")
|
||||
local util = require("json.decode.util")
|
||||
local merge = jsonutil.merge
|
||||
|
||||
local tonumber = tonumber
|
||||
local string_char = require("string").char
|
||||
local floor = require("math").floor
|
||||
local table_concat = require("table").concat
|
||||
|
||||
local error = error
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local function get_error(item)
|
||||
local fmt_string = item .. " in string [%q] @ %i:%i"
|
||||
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) * 1
|
||||
end
|
||||
|
||||
local bad_unicode = get_error("Illegal unicode escape")
|
||||
local bad_hex = get_error("Illegal hex escape")
|
||||
local bad_character = get_error("Illegal character")
|
||||
local bad_escape = get_error("Illegal escape")
|
||||
|
||||
local knownReplacements = {
|
||||
["'"] = "'",
|
||||
['"'] = '"',
|
||||
['\\'] = '\\',
|
||||
['/'] = '/',
|
||||
b = '\b',
|
||||
f = '\f',
|
||||
n = '\n',
|
||||
r = '\r',
|
||||
t = '\t',
|
||||
v = '\v',
|
||||
z = '\z'
|
||||
}
|
||||
|
||||
-- according to the table at http://da.wikipedia.org/wiki/UTF-8
|
||||
local function utf8DecodeUnicode(code1, code2)
|
||||
code1, code2 = tonumber(code1, 16), tonumber(code2, 16)
|
||||
if code1 == 0 and code2 < 0x80 then
|
||||
return string_char(code2)
|
||||
end
|
||||
if code1 < 0x08 then
|
||||
return string_char(
|
||||
0xC0 + code1 * 4 + floor(code2 / 64),
|
||||
0x80 + code2 % 64)
|
||||
end
|
||||
return string_char(
|
||||
0xE0 + floor(code1 / 16),
|
||||
0x80 + (code1 % 16) * 4 + floor(code2 / 64),
|
||||
0x80 + code2 % 64)
|
||||
end
|
||||
|
||||
local function decodeX(code)
|
||||
code = tonumber(code, 16)
|
||||
return string_char(code)
|
||||
end
|
||||
|
||||
local doSimpleSub = lpeg.C(lpeg.S("'\"\\/bfnrtvz")) / knownReplacements
|
||||
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 = '',
|
||||
additionalEscapes = false, -- disallow untranslated escapes
|
||||
escapeCheck = #lpeg.S('bfnrtv/\\"xu\'z'), -- no check on valid characters
|
||||
decodeUnicode = utf8DecodeUnicode,
|
||||
strict_quotes = false
|
||||
}
|
||||
|
||||
local modeOptions = {}
|
||||
|
||||
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)
|
||||
-- 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 generateLexer(options)
|
||||
options = options.strings
|
||||
local quotes = { '"' }
|
||||
if not options.strict_quotes then
|
||||
quotes[#quotes + 1] = "'"
|
||||
end
|
||||
local escapeMatch = doSimpleSub
|
||||
escapeMatch = escapeMatch + doXSub / decodeX
|
||||
escapeMatch = escapeMatch + doUniSub / options.decodeUnicode
|
||||
if options.escapeCheck then
|
||||
escapeMatch = options.escapeCheck * escapeMatch + bad_escape
|
||||
end
|
||||
if options.additionalEscapes then
|
||||
escapeMatch = options.additionalEscapes + escapeMatch
|
||||
end
|
||||
local captureString
|
||||
for i = 1, #quotes do
|
||||
local cap = buildCaptureString(quotes[i], options.badChars, escapeMatch)
|
||||
if captureString == nil then
|
||||
captureString = cap
|
||||
else
|
||||
captureString = captureString + cap
|
||||
end
|
||||
end
|
||||
return captureString
|
||||
end
|
||||
|
||||
local strings = {
|
||||
mergeOptions = mergeOptions,
|
||||
generateLexer = generateLexer
|
||||
}
|
||||
|
||||
return strings
|
121
tools/luajson/json/decode/util.lua
Normal file
121
tools/luajson/json/decode/util.lua
Normal file
@ -0,0 +1,121 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local lpeg = require("lpeg")
|
||||
local select = select
|
||||
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
|
||||
|
||||
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
|
||||
local ascii_space = lpeg.S("\t\n\v\f\r ")
|
||||
local unicode_space
|
||||
do
|
||||
local chr = string_char
|
||||
local u_space = ascii_space
|
||||
-- \u0085 \u00A0
|
||||
u_space = u_space + lpeg.P(chr(0xC2)) * lpeg.S(chr(0x85) .. chr(0xA0))
|
||||
-- \u1680 \u180E
|
||||
u_space = u_space + lpeg.P(chr(0xE1)) * (lpeg.P(chr(0x9A, 0x80)) + chr(0xA0, 0x8E))
|
||||
-- \u2000 - \u200A, also 200B
|
||||
local spacing_end = ""
|
||||
for i = 0x80,0x8b do
|
||||
spacing_end = spacing_end .. chr(i)
|
||||
end
|
||||
-- \u2028 \u2029 \u202F
|
||||
spacing_end = spacing_end .. chr(0xA8) .. chr(0xA9) .. chr(0xAF)
|
||||
u_space = u_space + lpeg.P(chr(0xE2, 0x80)) * lpeg.S(spacing_end)
|
||||
-- \u205F
|
||||
u_space = u_space + lpeg.P(chr(0xE2, 0x81, 0x9F))
|
||||
-- \u3000
|
||||
u_space = u_space + lpeg.P(chr(0xE3, 0x80, 0x80))
|
||||
-- BOM \uFEFF
|
||||
u_space = u_space + lpeg.P(chr(0xEF, 0xBB, 0xBF))
|
||||
unicode_space = u_space
|
||||
end
|
||||
|
||||
local identifier = lpeg.R("AZ","az","__") * lpeg.R("AZ","az", "__", "09") ^0
|
||||
|
||||
local hex = lpeg.R("09","AF","af")
|
||||
local hexpair = hex * hex
|
||||
|
||||
local comments = {
|
||||
cpp = lpeg.P("//") * (1 - lpeg.P("\n"))^0 * lpeg.P("\n"),
|
||||
c = lpeg.P("/*") * (1 - lpeg.P("*/"))^0 * lpeg.P("*/")
|
||||
}
|
||||
|
||||
local comment = comments.cpp + comments.c
|
||||
|
||||
local ascii_ignored = (ascii_space + comment)^0
|
||||
|
||||
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
|
||||
local DecimalLpegVersion = lpeg.version and tonumber(lpeg.version():match("^(%d+%.%d+)")) or 0.7
|
||||
|
||||
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
|
161
tools/luajson/json/encode.lua
Normal file
161
tools/luajson/json/encode.lua
Normal file
@ -0,0 +1,161 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local type = type
|
||||
local assert, error = assert, error
|
||||
local getmetatable, setmetatable = getmetatable, setmetatable
|
||||
|
||||
local ipairs, pairs = ipairs, pairs
|
||||
local require = require
|
||||
|
||||
local output = require("json.encode.output")
|
||||
|
||||
local util = require("json.util")
|
||||
local util_merge, isCall = util.merge, util.isCall
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
--[[
|
||||
List of encoding modules to load.
|
||||
Loaded in sequence such that earlier encoders get priority when
|
||||
duplicate type-handlers exist.
|
||||
]]
|
||||
local modulesToLoad = {
|
||||
"strings",
|
||||
"number",
|
||||
"calls",
|
||||
"others",
|
||||
"array",
|
||||
"object"
|
||||
}
|
||||
-- Modules that have been loaded
|
||||
local loadedModules = {}
|
||||
|
||||
local json_encode = {}
|
||||
|
||||
-- Configuration bases for client apps
|
||||
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)
|
||||
if mod.mergeOptions then
|
||||
for _, mode in pairs(modes_defined) do
|
||||
mod.mergeOptions(json_encode[mode], mode)
|
||||
end
|
||||
end
|
||||
loadedModules[name] = mod
|
||||
end
|
||||
|
||||
-- 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
|
||||
local function prepareEncodeMap(options)
|
||||
local map = {}
|
||||
for _, name in ipairs(modulesToLoad) do
|
||||
local encodermap = loadedModules[name].getEncoder(options[name])
|
||||
for valueType, encoderSet in pairs(encodermap) do
|
||||
map[valueType] = flattenOutput(map[valueType], encoderSet)
|
||||
end
|
||||
end
|
||||
return map
|
||||
end
|
||||
|
||||
--[[
|
||||
Encode a value with a given encoding map and state
|
||||
]]
|
||||
local function encodeWithMap(value, map, state, isObjectKey)
|
||||
local t = type(value)
|
||||
local encoderList = assert(map[t], "Failed to encode value, unhandled type: " .. t)
|
||||
for _, encoder in ipairs(encoderList) do
|
||||
local ret = encoder(value, state, isObjectKey)
|
||||
if false ~= ret then
|
||||
return ret
|
||||
end
|
||||
end
|
||||
error("Failed to encode value, encoders for " .. t .. " deny encoding")
|
||||
end
|
||||
|
||||
|
||||
local function getBaseEncoder(options)
|
||||
local encoderMap = prepareEncodeMap(options)
|
||||
if options.preProcess then
|
||||
local preProcess = options.preProcess
|
||||
return function(value, state, isObjectKey)
|
||||
local ret = preProcess(value, isObjectKey or false)
|
||||
if nil ~= ret then
|
||||
value = ret
|
||||
end
|
||||
return encodeWithMap(value, encoderMap, state)
|
||||
end
|
||||
end
|
||||
return function(value, state, isObjectKey)
|
||||
return encodeWithMap(value, encoderMap, state)
|
||||
end
|
||||
end
|
||||
--[[
|
||||
Retreive an initial encoder instance based on provided options
|
||||
the initial encoder is responsible for initializing state
|
||||
State has at least these values configured: encode, check_unique, already_encoded
|
||||
]]
|
||||
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)
|
||||
if options.initialObject then
|
||||
local errorMessage = "Invalid arguments: expects a JSON Object or Array at the root"
|
||||
assert(type(value) == 'table' and not isCall(value, options), errorMessage)
|
||||
end
|
||||
|
||||
local alreadyEncoded = {}
|
||||
local function check_unique(value)
|
||||
assert(not alreadyEncoded[value], "Recursive encoding of value")
|
||||
alreadyEncoded[value] = true
|
||||
end
|
||||
|
||||
local outputEncoder = options.output and options.output() or output.getDefault()
|
||||
local state = {
|
||||
encode = encode,
|
||||
check_unique = check_unique,
|
||||
already_encoded = alreadyEncoded, -- To unmark encoding when moving up stack
|
||||
outputEncoder = outputEncoder
|
||||
}
|
||||
local ret = encode(value, state)
|
||||
if nil ~= ret then
|
||||
return outputEncoder.simple and outputEncoder.simple(ret) or ret
|
||||
end
|
||||
end
|
||||
return initialEncode
|
||||
end
|
||||
|
||||
-- CONSTRUCT STATE WITH FOLLOWING (at least)
|
||||
--[[
|
||||
encoder
|
||||
check_unique -- used by inner encoders to make sure value is unique
|
||||
already_encoded -- used to unmark a value as unique
|
||||
]]
|
||||
function json_encode.encode(data, options)
|
||||
return json_encode.getEncoder(options)(data)
|
||||
end
|
||||
|
||||
local mt = {}
|
||||
mt.__call = function(self, ...)
|
||||
return json_encode.encode(...)
|
||||
end
|
||||
|
||||
setmetatable(json_encode, mt)
|
||||
|
||||
return json_encode
|
110
tools/luajson/json/encode/array.lua
Normal file
110
tools/luajson/json/encode/array.lua
Normal file
@ -0,0 +1,110 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local jsonutil = require("json.util")
|
||||
|
||||
local type = type
|
||||
local pairs = pairs
|
||||
local assert = assert
|
||||
|
||||
local table = require("table")
|
||||
local math = require("math")
|
||||
local table_concat = table.concat
|
||||
local math_floor, math_modf = math.floor, math.modf
|
||||
|
||||
local jsonutil = require("json.util")
|
||||
local util_IsArray = jsonutil.IsArray
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local defaultOptions = {
|
||||
isArray = util_IsArray
|
||||
}
|
||||
|
||||
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.
|
||||
Criteria for it being an array:
|
||||
* ExternalIsArray returns true (or false directly reports not-array)
|
||||
* If the table has an 'n' value that is an integer >= 1 then it
|
||||
is an array... may result in false positives (should check some values
|
||||
before it)
|
||||
* It is a contiguous list of values with zero string-based keys
|
||||
]]
|
||||
local function isArray(val, options)
|
||||
local externalIsArray = options and options.isArray
|
||||
|
||||
if externalIsArray then
|
||||
local ret = externalIsArray(val)
|
||||
if ret == true or ret == false then
|
||||
return ret
|
||||
end
|
||||
end
|
||||
-- Use the 'n' element if it's a number
|
||||
if type(val.n) == 'number' and math_floor(val.n) == val.n and val.n >= 1 then
|
||||
return true
|
||||
end
|
||||
local len = #val
|
||||
for k,v in pairs(val) do
|
||||
if type(k) ~= 'number' then
|
||||
return false
|
||||
end
|
||||
local _, decim = math_modf(k)
|
||||
if not (decim == 0 and 1<=k) then
|
||||
return false
|
||||
end
|
||||
if k > len then -- Use Lua's length as absolute determiner
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
Cleanup function to unmark a value as in the encoding process and return
|
||||
trailing results
|
||||
]]
|
||||
local function unmarkAfterEncode(tab, state, ...)
|
||||
state.already_encoded[tab] = nil
|
||||
return ...
|
||||
end
|
||||
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
|
||||
end
|
||||
-- Make sure this value hasn't been encoded yet
|
||||
state.check_unique(tab)
|
||||
local encode = state.encode
|
||||
local compositeEncoder = state.outputEncoder.composite
|
||||
local valueEncoder = [[
|
||||
for i = 1, (composite.n or #composite) do
|
||||
local val = composite[i]
|
||||
PUTINNER(i ~= 1)
|
||||
val = encode(val, state)
|
||||
val = val or ''
|
||||
if val then
|
||||
PUTVALUE(val)
|
||||
end
|
||||
end
|
||||
]]
|
||||
return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '[', ']', ',', tab, encode, state))
|
||||
end
|
||||
return { table = encodeArray }
|
||||
end
|
||||
|
||||
local array = {
|
||||
mergeOptions = mergeOptions,
|
||||
isArray = isArray,
|
||||
getEncoder = getEncoder
|
||||
}
|
||||
|
||||
return array
|
68
tools/luajson/json/encode/calls.lua
Normal file
68
tools/luajson/json/encode/calls.lua
Normal file
@ -0,0 +1,68 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local table = require("table")
|
||||
local table_concat = table.concat
|
||||
|
||||
local select = select
|
||||
local getmetatable, setmetatable = getmetatable, setmetatable
|
||||
local assert = assert
|
||||
|
||||
local jsonutil = require("json.util")
|
||||
|
||||
local isCall, decodeCall = jsonutil.isCall, jsonutil.decodeCall
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local defaultOptions = {
|
||||
}
|
||||
|
||||
-- No real default-option handling needed...
|
||||
local modeOptions = {}
|
||||
|
||||
local function mergeOptions(options, mode)
|
||||
jsonutil.doOptionMerge(options, false, 'calls', defaultOptions, mode and modeOptions[mode])
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Encodes 'value' as a function call
|
||||
Must have parameters in the 'callData' field of the metatable
|
||||
name == name of the function call
|
||||
parameters == array of parameters to encode
|
||||
]]
|
||||
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
|
||||
end
|
||||
local encode = state.encode
|
||||
local name, params = decodeCall(value)
|
||||
local compositeEncoder = state.outputEncoder.composite
|
||||
local valueEncoder = [[
|
||||
for i = 1, (composite.n or #composite) do
|
||||
local val = composite[i]
|
||||
PUTINNER(i ~= 1)
|
||||
val = encode(val, state)
|
||||
val = val or ''
|
||||
if val then
|
||||
PUTVALUE(val)
|
||||
end
|
||||
end
|
||||
]]
|
||||
return compositeEncoder(valueEncoder, name .. '(', ')', ',', params, encode, state)
|
||||
end
|
||||
return {
|
||||
table = encodeCall,
|
||||
['function'] = encodeCall
|
||||
}
|
||||
end
|
||||
|
||||
local calls = {
|
||||
mergeOptions = mergeOptions,
|
||||
getEncoder = getEncoder
|
||||
}
|
||||
|
||||
return calls
|
58
tools/luajson/json/encode/number.lua
Normal file
58
tools/luajson/json/encode/number.lua
Normal file
@ -0,0 +1,58 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local tostring = tostring
|
||||
local assert = assert
|
||||
local jsonutil = require("json.util")
|
||||
local huge = require("math").huge
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local defaultOptions = {
|
||||
nan = true,
|
||||
inf = true
|
||||
}
|
||||
|
||||
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")
|
||||
return "NaN"
|
||||
end
|
||||
if number == huge then
|
||||
assert(options.inf, "Invalid number: Infinity not enabled")
|
||||
return "Infinity"
|
||||
end
|
||||
if number == -huge then
|
||||
assert(options.inf, "Invalid number: Infinity not enabled")
|
||||
return "-Infinity"
|
||||
end
|
||||
return tostring(number)
|
||||
end
|
||||
|
||||
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
|
77
tools/luajson/json/encode/object.lua
Normal file
77
tools/luajson/json/encode/object.lua
Normal file
@ -0,0 +1,77 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local pairs = pairs
|
||||
local assert = assert
|
||||
|
||||
local type = type
|
||||
local tostring = tostring
|
||||
|
||||
local table_concat = require("table").concat
|
||||
local jsonutil = require("json.util")
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local defaultOptions = {
|
||||
}
|
||||
|
||||
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
|
||||
trailing results
|
||||
]]
|
||||
local function unmarkAfterEncode(tab, state, ...)
|
||||
state.already_encoded[tab] = nil
|
||||
return ...
|
||||
end
|
||||
--[[
|
||||
Encode a table as a JSON Object ( keys = strings, values = anything else )
|
||||
]]
|
||||
local function encodeTable(tab, options, state)
|
||||
-- Make sure this value hasn't been encoded yet
|
||||
state.check_unique(tab)
|
||||
local encode = state.encode
|
||||
local compositeEncoder = state.outputEncoder.composite
|
||||
local valueEncoder = [[
|
||||
local first = true
|
||||
for k, v in pairs(composite) do
|
||||
local ti = type(k)
|
||||
assert(ti == 'string' or ti == 'number' or ti == 'boolean', "Invalid object index type: " .. ti)
|
||||
local name = encode(tostring(k), state, true)
|
||||
if first then
|
||||
first = false
|
||||
else
|
||||
name = ',' .. name
|
||||
end
|
||||
PUTVALUE(name .. ':')
|
||||
local val = encode(v, state)
|
||||
val = val or ''
|
||||
if val then
|
||||
PUTVALUE(val)
|
||||
end
|
||||
end
|
||||
]]
|
||||
return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '{', '}', nil, tab, encode, state))
|
||||
end
|
||||
|
||||
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
|
66
tools/luajson/json/encode/others.lua
Normal file
66
tools/luajson/json/encode/others.lua
Normal file
@ -0,0 +1,66 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local tostring = tostring
|
||||
|
||||
local assert = assert
|
||||
local jsonutil = require("json.util")
|
||||
local type = type
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
-- Shortcut that works
|
||||
local encodeBoolean = tostring
|
||||
|
||||
local defaultOptions = {
|
||||
allowUndefined = true,
|
||||
null = jsonutil.null,
|
||||
undefined = jsonutil.undefined
|
||||
}
|
||||
|
||||
local modeOptions = {}
|
||||
|
||||
modeOptions.strict = {
|
||||
allowUndefined = false
|
||||
}
|
||||
|
||||
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'
|
||||
elseif value == options.undefined then
|
||||
assert(options.allowUndefined, "Invalid value: Unsupported 'Undefined' parameter")
|
||||
return 'undefined'
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
local function encodeBoolean(value, state)
|
||||
return value and 'true' or 'false'
|
||||
end
|
||||
local nullType = type(options.null)
|
||||
local undefinedType = options.undefined and type(options.undefined)
|
||||
-- Make sure that all of the types handled here are handled
|
||||
local ret = {
|
||||
boolean = encodeBoolean,
|
||||
['nil'] = function() return 'null' end,
|
||||
[nullType] = encodeOthers
|
||||
}
|
||||
if undefinedType then
|
||||
ret[undefinedType] = encodeOthers
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local others = {
|
||||
encodeBoolean = encodeBoolean,
|
||||
mergeOptions = mergeOptions,
|
||||
getEncoder = getEncoder
|
||||
}
|
||||
|
||||
return others
|
91
tools/luajson/json/encode/output.lua
Normal file
91
tools/luajson/json/encode/output.lua
Normal file
@ -0,0 +1,91 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local type = type
|
||||
local assert, error = assert, error
|
||||
local table_concat = require("table").concat
|
||||
local loadstring = loadstring or load
|
||||
|
||||
local io = require("io")
|
||||
|
||||
local setmetatable = setmetatable
|
||||
|
||||
local output_utility = require("json.encode.output_utility")
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local tableCompositeCache = setmetatable({}, {__mode = 'v'})
|
||||
|
||||
local TABLE_VALUE_WRITER = [[
|
||||
ret[#ret + 1] = %VALUE%
|
||||
]]
|
||||
|
||||
local TABLE_INNER_WRITER = ""
|
||||
|
||||
--[[
|
||||
nextValues can output a max of two values to throw into the data stream
|
||||
expected to be called until nil is first return value
|
||||
value separator should either be attached to v1 or in innerValue
|
||||
]]
|
||||
local function defaultTableCompositeWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state)
|
||||
if type(nextValues) == 'string' then
|
||||
local fun = output_utility.prepareEncoder(defaultTableCompositeWriter, nextValues, innerValue, TABLE_VALUE_WRITER, TABLE_INNER_WRITER)
|
||||
local ret = {}
|
||||
fun(composite, ret, encode, state)
|
||||
return beginValue .. table_concat(ret, innerValue) .. closeValue
|
||||
end
|
||||
end
|
||||
|
||||
-- no 'simple' as default action is just to return the value
|
||||
local function getDefault()
|
||||
return { composite = defaultTableCompositeWriter }
|
||||
end
|
||||
|
||||
-- BEGIN IO-WRITER OUTPUT
|
||||
local IO_INNER_WRITER = [[
|
||||
if %WRITE_INNER% then
|
||||
state.__outputFile:write(%INNER_VALUE%)
|
||||
end
|
||||
]]
|
||||
local IO_VALUE_WRITER = [[
|
||||
state.__outputFile:write(%VALUE%)
|
||||
]]
|
||||
|
||||
local function buildIoWriter(output)
|
||||
if not output then -- Default to stdout
|
||||
output = io.output()
|
||||
end
|
||||
local function ioWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state)
|
||||
-- HOOK OUTPUT STATE
|
||||
state.__outputFile = output
|
||||
if type(nextValues) == 'string' then
|
||||
local fun = output_utility.prepareEncoder(ioWriter, nextValues, innerValue, IO_VALUE_WRITER, IO_INNER_WRITER)
|
||||
local ret = {}
|
||||
output:write(beginValue)
|
||||
fun(composite, ret, encode, state)
|
||||
output:write(closeValue)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function ioSimpleWriter(encoded)
|
||||
if encoded then
|
||||
output:write(encoded)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
return { composite = ioWriter, simple = ioSimpleWriter }
|
||||
end
|
||||
local function getIoWriter(output)
|
||||
return function()
|
||||
return buildIoWriter(output)
|
||||
end
|
||||
end
|
||||
|
||||
local output = {
|
||||
getDefault = getDefault,
|
||||
getIoWriter = getIoWriter
|
||||
}
|
||||
|
||||
return output
|
54
tools/luajson/json/encode/output_utility.lua
Normal file
54
tools/luajson/json/encode/output_utility.lua
Normal file
@ -0,0 +1,54 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local setmetatable = setmetatable
|
||||
local assert, loadstring = assert, loadstring or load
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
-- Key == weak, if main key goes away, then cache cleared
|
||||
local outputCache = setmetatable({}, {__mode = 'k'})
|
||||
-- TODO: inner tables weak?
|
||||
|
||||
local function buildFunction(nextValues, innerValue, valueWriter, innerWriter)
|
||||
local putInner = ""
|
||||
if innerValue and innerWriter then
|
||||
-- Prepare the lua-string representation of the separator to put in between values
|
||||
local formattedInnerValue = ("%q"):format(innerValue)
|
||||
-- Fill in the condition %WRITE_INNER% and the %INNER_VALUE% to actually write
|
||||
putInner = innerWriter:gsub("%%WRITE_INNER%%", "%%1"):gsub("%%INNER_VALUE%%", formattedInnerValue)
|
||||
end
|
||||
-- Template-in the value writer (if present) and its conditional argument
|
||||
local functionCode = nextValues:gsub("PUTINNER(%b())", putInner)
|
||||
-- %VALUE% is to be filled in by the value-to-write
|
||||
valueWriter = valueWriter:gsub("%%VALUE%%", "%%1")
|
||||
-- Template-in the value writer with its argument
|
||||
functionCode = functionCode:gsub("PUTVALUE(%b())", valueWriter)
|
||||
functionCode = [[
|
||||
return function(composite, ret, encode, state)
|
||||
]] .. functionCode .. [[
|
||||
end
|
||||
]]
|
||||
return assert(loadstring(functionCode))()
|
||||
end
|
||||
|
||||
local function prepareEncoder(cacheKey, nextValues, innerValue, valueWriter, innerWriter)
|
||||
local cache = outputCache[cacheKey]
|
||||
if not cache then
|
||||
cache = {}
|
||||
outputCache[cacheKey] = cache
|
||||
end
|
||||
local fun = cache[nextValues]
|
||||
if not fun then
|
||||
fun = buildFunction(nextValues, innerValue, valueWriter, innerWriter)
|
||||
cache[nextValues] = fun
|
||||
end
|
||||
return fun
|
||||
end
|
||||
|
||||
local output_utility = {
|
||||
prepareEncoder = prepareEncoder
|
||||
}
|
||||
|
||||
return output_utility
|
88
tools/luajson/json/encode/strings.lua
Normal file
88
tools/luajson/json/encode/strings.lua
Normal file
@ -0,0 +1,88 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local string_char = require("string").char
|
||||
local pairs = pairs
|
||||
|
||||
local jsonutil = require("json.util")
|
||||
local util_merge = jsonutil.merge
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local normalEncodingMap = {
|
||||
['"'] = '\\"',
|
||||
['\\'] = '\\\\',
|
||||
['/'] = '\\/',
|
||||
['\b'] = '\\b',
|
||||
['\f'] = '\\f',
|
||||
['\n'] = '\\n',
|
||||
['\r'] = '\\r',
|
||||
['\t'] = '\\t',
|
||||
['\v'] = '\\v' -- not in official spec, on report, removing
|
||||
}
|
||||
|
||||
local xEncodingMap = {}
|
||||
for char, encoded in pairs(normalEncodingMap) do
|
||||
xEncodingMap[char] = encoded
|
||||
end
|
||||
|
||||
-- Pre-encode the control characters to speed up encoding...
|
||||
-- NOTE: UTF-8 may not work out right w/ JavaScript
|
||||
-- JavaScript uses 2 bytes after a \u... yet UTF-8 is a
|
||||
-- byte-stream encoding, not pairs of bytes (it does encode
|
||||
-- some letters > 1 byte, but base case is 1)
|
||||
for i = 0, 255 do
|
||||
local c = string_char(i)
|
||||
if c:match('[%z\1-\031\128-\255]') and not normalEncodingMap[c] then
|
||||
-- WARN: UTF8 specializes values >= 0x80 as parts of sequences...
|
||||
-- without \x encoding, do not allow encoding > 7F
|
||||
normalEncodingMap[c] = ('\\u%.4X'):format(i)
|
||||
xEncodingMap[c] = ('\\x%.2X'):format(i)
|
||||
end
|
||||
end
|
||||
|
||||
local defaultOptions = {
|
||||
xEncode = false, -- Encode single-bytes as \xXX
|
||||
processor = nil, -- Simple processor for the string prior to quoting
|
||||
-- / is not required to be quoted but it helps with certain decoding
|
||||
-- Required encoded characters, " \, and 00-1F (0 - 31)
|
||||
encodeSet = '\\"/%z\1-\031',
|
||||
encodeSetAppend = nil -- Chars to append to the default set
|
||||
}
|
||||
|
||||
local modeOptions = {}
|
||||
|
||||
local function mergeOptions(options, mode)
|
||||
jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode])
|
||||
end
|
||||
|
||||
local function getEncoder(options)
|
||||
options = options and util_merge({}, defaultOptions, options) or defaultOptions
|
||||
local encodeSet = options.encodeSet
|
||||
if options.encodeSetAppend then
|
||||
encodeSet = encodeSet .. options.encodeSetAppend
|
||||
end
|
||||
local encodingMap = options.xEncode and xEncodingMap or normalEncodingMap
|
||||
local encodeString
|
||||
if options.processor then
|
||||
local processor = options.processor
|
||||
encodeString = function(s, state)
|
||||
return '"' .. processor(s:gsub('[' .. encodeSet .. ']', encodingMap)) .. '"'
|
||||
end
|
||||
else
|
||||
encodeString = function(s, state)
|
||||
return '"' .. s:gsub('[' .. encodeSet .. ']', encodingMap) .. '"'
|
||||
end
|
||||
end
|
||||
return {
|
||||
string = encodeString
|
||||
}
|
||||
end
|
||||
|
||||
local strings = {
|
||||
mergeOptions = mergeOptions,
|
||||
getEncoder = getEncoder
|
||||
}
|
||||
|
||||
return strings
|
152
tools/luajson/json/util.lua
Normal file
152
tools/luajson/json/util.lua
Normal file
@ -0,0 +1,152 @@
|
||||
--[[
|
||||
Licensed according to the included 'LICENSE' document
|
||||
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||
]]
|
||||
local type = type
|
||||
local print = print
|
||||
local tostring = tostring
|
||||
local pairs = pairs
|
||||
local getmetatable, setmetatable = getmetatable, setmetatable
|
||||
local select = select
|
||||
|
||||
local _ENV = nil
|
||||
|
||||
local function foreach(tab, func)
|
||||
for k, v in pairs(tab) do
|
||||
func(k,v)
|
||||
end
|
||||
end
|
||||
local function printValue(tab, name)
|
||||
local parsed = {}
|
||||
local function doPrint(key, value, space)
|
||||
space = space or ''
|
||||
if type(value) == 'table' then
|
||||
if parsed[value] then
|
||||
print(space .. key .. '= <' .. parsed[value] .. '>')
|
||||
else
|
||||
parsed[value] = key
|
||||
print(space .. key .. '= {')
|
||||
space = space .. ' '
|
||||
foreach(value, function(key, value) doPrint(key, value, space) end)
|
||||
end
|
||||
else
|
||||
if type(value) == 'string' then
|
||||
value = '[[' .. tostring(value) .. ']]'
|
||||
end
|
||||
print(space .. key .. '=' .. tostring(value))
|
||||
end
|
||||
end
|
||||
doPrint(name, tab)
|
||||
end
|
||||
|
||||
local function clone(t)
|
||||
local ret = {}
|
||||
for k,v in pairs(t) do
|
||||
ret[k] = v
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function inner_merge(t, remaining, from, ...)
|
||||
if remaining == 0 then
|
||||
return t
|
||||
end
|
||||
if from then
|
||||
for k,v in pairs(from) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
-- Function to insert nulls into the JSON stream
|
||||
local function null()
|
||||
return null
|
||||
end
|
||||
|
||||
-- Marker for 'undefined' values
|
||||
local function undefined()
|
||||
return undefined
|
||||
end
|
||||
|
||||
local ArrayMT = {}
|
||||
|
||||
--[[
|
||||
Return's true if the metatable marks it as an array..
|
||||
Or false if it has no array component at all
|
||||
Otherwise nil to get the normal detection component working
|
||||
]]
|
||||
local function IsArray(value)
|
||||
if type(value) ~= 'table' then return false end
|
||||
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
|
||||
local function InitArray(array)
|
||||
setmetatable(array, ArrayMT)
|
||||
return array
|
||||
end
|
||||
|
||||
local CallMT = {}
|
||||
|
||||
local function isCall(value)
|
||||
return CallMT == getmetatable(value)
|
||||
end
|
||||
|
||||
local function buildCall(name, ...)
|
||||
local callData = {
|
||||
name = name,
|
||||
parameters = {n = select('#', ...), ...}
|
||||
}
|
||||
return setmetatable(callData, CallMT)
|
||||
end
|
||||
|
||||
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
|
95
tools/luajson/test_json.lua
Normal file
95
tools/luajson/test_json.lua
Normal file
@ -0,0 +1,95 @@
|
||||
package.path = package.path .. ";lua_scripts/libraries/luajson/?.lua"
|
||||
|
||||
local JSON = require"json"
|
||||
|
||||
local jsontest = [[{ 1:{"scn_ptz_id":"",
|
||||
"scn_ptz_prepos":"Preset 176",
|
||||
"scn_ptz_order":1,
|
||||
"scn_ptz_duration":"30",
|
||||
"scn_ptz_rally_delay":"2"}
|
||||
,
|
||||
2:{"scn_ptz_id":"","scn_ptz_prepos":"route","scn_ptz_order":2,"scn_ptz_duration":"30","scn_ptz_rally_delay":"2"} }
|
||||
]]
|
||||
local jsontest2 = [[{
|
||||
"extension":"mpg",
|
||||
"id":1545148451781,
|
||||
"name":"Foule_1280x720p.mpg",
|
||||
"size":67240746,
|
||||
"date":1545148451,
|
||||
"mime":"video\/mpeg",
|
||||
"filename":"1545148451781.mpg",
|
||||
"dir":"\/home\/pixalarm_data\/fileprocessor_data",
|
||||
"function_metadatas":
|
||||
{
|
||||
"function_faceblur":
|
||||
{
|
||||
"date":1545228627,
|
||||
"current_frame":"845",
|
||||
"polygons":[
|
||||
{
|
||||
"polygon_id":"new_1",
|
||||
"polygon_vertex":"[
|
||||
[0.14254859611231102,0.12476007677543186],[0.13174946004319654,0.4740882917466411],
|
||||
[0.3898488120950324,0.6621880998080614],[0.4038876889848812,0.11516314779270634]
|
||||
]",
|
||||
"polygon_frame_start":"1",
|
||||
"polygon_frame_stop":"300",
|
||||
"polygon_type":"full_blur"
|
||||
},
|
||||
{
|
||||
"polygon_id":"new_2",
|
||||
"polygon_vertex":"[
|
||||
[0.6198704103671706,0.1727447216890595],[0.5496760259179265,0.6007677543186181],
|
||||
[0.7775377969762419,0.7946257197696737],[0.9028077753779697,0.761996161228407],
|
||||
[0.9481641468682506,0.2821497120921305],[0.7829373650107991,0.04798464491362764]
|
||||
]",
|
||||
"polygon_frame_start":"200",
|
||||
"polygon_frame_stop":"845",
|
||||
"polygon_type":"no_blur"
|
||||
}
|
||||
],
|
||||
"framecuts":[
|
||||
["17","110"],
|
||||
["248","298"],
|
||||
["488","620"],
|
||||
["378","428"]
|
||||
],
|
||||
"face_selection":[
|
||||
{
|
||||
"frame":"21",
|
||||
"x":"0.5",
|
||||
"y":"0.356"
|
||||
},
|
||||
{
|
||||
"frame":"108",
|
||||
"x":"0.4289",
|
||||
"y":"0.275"
|
||||
},
|
||||
{
|
||||
"frame":"294",
|
||||
"x":"0.726",
|
||||
"y":"0.2364"
|
||||
}
|
||||
],
|
||||
"blur_type":"blur",
|
||||
"blur_area":"face"
|
||||
}
|
||||
},
|
||||
"total_frame":"845",
|
||||
"status":"DECODE_FINISHED",
|
||||
"fps":"25.00"
|
||||
}]]
|
||||
|
||||
local res = JSON.decode(jsontest2)
|
||||
for k, v in pairs(res) do
|
||||
print( k, v)
|
||||
end
|
||||
|
||||
res = JSON.decode( '{"content" : {},"date" : "2014-12-30T08:29:48Z","error" : {"code" : 0,"httpcode" : 200,"message" : ""},"status" : 1}' )
|
||||
for k, v in pairs(res) do
|
||||
print( k, v)
|
||||
end
|
||||
|
||||
local jsondata = JSON.encode( res )
|
||||
print(jsondata)
|
||||
|
Reference in New Issue
Block a user