Update talents + beasts

This commit is contained in:
2020-04-07 18:02:56 +02:00
parent e8b4ed9ad6
commit c044c52b52
31 changed files with 25178 additions and 18136 deletions

1
tools/beasts.json Normal file

File diff suppressed because one or more lines are too long

View 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 dimpressionnants 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 à lextrê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> &nbsp;<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>"
}
]
}

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

File diff suppressed because it is too large Load Diff

1373
tools/lpeg/lpeg.lua Normal file

File diff suppressed because it is too large Load Diff

356
tools/lpeg/lpprint.lua Normal file
View 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

File diff suppressed because it is too large Load Diff

286
tools/lpeg/re.lua Normal file
View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

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

View 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)