Update talents + beasts
This commit is contained in:
parent
e8b4ed9ad6
commit
c044c52b52
@ -37,13 +37,13 @@ Pour le compendium :
|
|||||||
|
|
||||||
**!!! NE COMMENCEZ PAS A TRADUIRE QUELQUE CHOSE SANS ME CONTACTER !!!**
|
**!!! NE COMMENCEZ PAS A TRADUIRE QUELQUE CHOSE SANS ME CONTACTER !!!**
|
||||||
|
|
||||||
**Traductions en cours au 06/04/2020**
|
**Traductions en cours au 07/04/2020**
|
||||||
|
|
||||||
* compendium/talents -> split en cours
|
* compendium/talents -> split en cours
|
||||||
-> Bless -> Jerôme Paire
|
-> Bless -> Jerôme Paire
|
||||||
**OK** -> Bookish:Contorsionist -> Dr.Droide
|
**OK** -> Bookish:Contorsionist -> Dr.Droide
|
||||||
-> Coolheaded:Embezzle -> Moilu
|
**OK** -> Coolheaded:Embezzle -> Moilu
|
||||||
-> Enclosed Fighter:Gregarious -> Gharazel
|
**OK** -> Enclosed Fighter:Gregarious -> Gharazel
|
||||||
**OK** -> Gunner:Kingpin -> Dr.Droide
|
**OK** -> Gunner:Kingpin -> Dr.Droide
|
||||||
|
|
||||||
* Divers ajustements de termes ici et là - Debug en cours
|
* Divers ajustements de termes ici et là - Debug en cours
|
||||||
|
File diff suppressed because one or more lines are too long
@ -159,152 +159,152 @@
|
|||||||
{
|
{
|
||||||
"id": "Coolheaded",
|
"id": "Coolheaded",
|
||||||
"name": "Imperturbable",
|
"name": "Imperturbable",
|
||||||
"description": "You gain a permanent +5 bonus to your starting Willpower Characteristic this does not count towards your Advances."
|
"description": "Vous gagnez un bonus permanent de +5 à votre Caractéristique FM de départ (ne comptent pas comme des Augmentations)."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Crack the Whip",
|
"id": "Crack the Whip",
|
||||||
"name": "Claquer le fouet",
|
"name": "Claquer le fouet",
|
||||||
"description": "You know how to get the most out of your animals. When an animal you control is Fleeing or Running, it gains +1 Movement if you are using a whip."
|
"description": "Vous savez comment tirer le meilleur parti de vos animaux. Lorsque l'un des animaux que vous contrôlez Fuit ou Court, il gagne +1 en Mouvement si vous utilisez un fouet."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Craftsman",
|
"id": "Craftsman",
|
||||||
"name": "Maitre Artisan",
|
"name": "Maitre Artisan",
|
||||||
"description": "You have true creative talent. Add the associated Trade Skill to any Career you enter. If the Trade Skill is already in your Career, you may instead purchase the Skill for 5 XP fewer per Advance."
|
"description": "Vous possédez un vrai talent créatif. Ajoutez la Compétence Métier associée à n'importe quelle Carrière que vous entamez. Si la Compétence Métier est déjà incluse dans votre Carrière, vous pouvez à la place acheter la Compétence pour 5 PX de moins par Augmentation."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Criminal",
|
"id": "Criminal",
|
||||||
"name": "Criminel",
|
"name": "Criminel",
|
||||||
"description": "<p>You are an active criminal making money from illegal sources, and you’re not always quiet about it. For the purposes of securing money, either when Earning during play or performing an Income Endeavour, refer to the following table:</p>\n<p> </p>\n<div>\n<table title=\"\" border=\"1\" summary=\"\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>\n<p>Career Level</p>\n</td>\n<td>\n<p>Bonus Money per time the Talent is taken</p>\n</td>\n</tr>\n<tr>\n<td>\n<p>1</p>\n</td>\n<td>\n<p>+2d10 brass pennies</p>\n</td>\n</tr>\n<tr>\n<td>\n<p>2</p>\n</td>\n<td>\n<p>+1d10 silver shillings</p>\n</td>\n</tr>\n<tr>\n<td>\n<p>3 </p>\n</td>\n<td>\n<p>+2d10 silver shillings</p>\n</td>\n</tr>\n<tr>\n<td>\n<p>4</p>\n</td>\n<td>\n<p>+1 gold crown</p>\n</td>\n</tr>\n</tbody>\n</table>\n</div>\n<p> </p>\n<p>Because of your obvious criminal nature, others consider you lower Status than them unless they also have the Criminal Talent, where Status is compared as normal — perhaps you have gang tattoos, look shifty, or are just rough around the edges, it’s your choice. Because of this, local law enforcers are always suspicious of you and suspect your motivations, which only gets worse the more times you have this Talent, with the exact implications determined by the GM. Lawbreakers without the Criminal Talent earn significantly less coin but are not obviously the sort to be breaking the law, so maintain their Status. With GM consent, you may spend XP to remove levels of the Criminal Talent for the same XP it cost to buy.</p>"
|
"description": "<p>Vous êtes un criminel actif qui gagne de l'argent grâce à des sources illégales, et vous n'êtes pas toujours discret à ce sujet. Pour calculer vos revenus, que ce soit quand vous Gagnez de l'argent ou quand vous effectuez une Activité Revenus, référez-vous au tableau suivant :</p>\n<p> </p>\n<div>\n<table title=\"\" border=\"1\" summary=\"\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>\n<p>Standing de Carrière</p>\n</td>\n<td>\n<p>Bonus d'argent par fois où le Talent est pris</p>\n</td>\n</tr>\n<tr>\n<td>\n<p>1</p>\n</td>\n<td>\n<p>+2d10 sous de cuivre</p>\n</td>\n</tr>\n<tr>\n<td>\n<p>2</p>\n</td>\n<td>\n<p>+1d10 pistoles d'argent</p>\n</td>\n</tr>\n<tr>\n<td>\n<p>3 </p>\n</td>\n<td>\n<p>+2d10 pistoles d'argent</p>\n</td>\n</tr>\n<tr>\n<td>\n<p>4</p>\n</td>\n<td>\n<p>+1 couronne d'or</p>\n</td>\n</tr>\n</tbody>\n</table>\n</div>\n<p> </p>\n<p>À cause de votre nature criminelle évidente, les autres vous considèrent d'un Statut inférieur, à moins qu'ils ne possèdent également le Talent Criminel. Dans ce cas, le Statut est comparé normalement - vous possédez peut-être des tatouages de gang, avez l'air louche, ou êtes juste un peu brute, vous avez le choix. A cause de cela, les forces de l'ordre locales se montre toujours méfiantes et doutent de vos motivations. Plus vous prenez ce Talent, plus cela empire, les répercussions exactes étant déterminées par le MJ. Ceux qui transgressent la loi sans posséder le Talent Criminel gagnent bien moins d'argent, mais comme ce n'est pas notoire, ils conservent donc leur Statut. Avec l'accord du MJ, vous pouvez retirer des niveaux du Talent Criminel pour le même montant de PX que leur coût d’achat.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Deadeye Shot",
|
"id": "Deadeye Shot",
|
||||||
"name": "Tir mortel",
|
"name": "Tir mortel",
|
||||||
"description": "<p>You always hit an opponent right between the eyes… or wherever else you intended to hit. Instead of reversing the dice to determine which Hit Location is struck with your ranged weapons, you may pick a location.</p>"
|
"description": "<p>Vous touchez toujours un adversaire juste entre les deux yeux... ou à l'endroit que vous aviez l'intention d'atteindre. Au lieu d'inverser le dé pour déterminer quelle Localisation est touchée avec votre arme à distance, vous pouvez la choisir.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Dealmaker",
|
"id": "Dealmaker",
|
||||||
"name": "Négociateur",
|
"name": "Négociateur",
|
||||||
"description": "<p>You are a skilled businessman who knows how to close a deal. When using the Haggle skill, you reduce or increase the price of the products by an extra 10%.</p>\n<p> </p>\n<p>Note: The GM may put a lower limit on prices here to show a seller refusing to sell below cost.</p>"
|
"description": "<p>Vous êtes un homme d'affaires expérimenté qui sait comment conclure un marché. Quand vous utilisez la Compétence Marchandage, vous réduisez ou augmentez les prix des produits de 10% supplémentaires.</p>\n<p> </p>\n<p>Remarque: le MJ peut imposer une réduction inférieure des prix pour montrer qu'un vendeur refuse de vendre à perte.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Detect Artifact",
|
"id": "Detect Artifact",
|
||||||
"name": "Détection d'artefact",
|
"name": "Détection d'artefact",
|
||||||
"description": "You are able to sense when magic lies within an artefact. You may attempt an Intuition Test for any magical artefact touched. If successful, you sense the item is magical; further, each SL also provides a specific special rule the item uses, if it has any. Normally, you may only attempt this Test once per artefact touched."
|
"description": "Vous êtes capable de déceler quand la magie réside dans un artefact. Vous pouvez tenter un Test d'Intuition pour n'importe quel artefact magique touché. Sur un succès, vous sentez que l'objet est magique ; de plus, chaque DR apprend également une règle spéciale spécifique concernant l'objet, s'il possède. En principe, vous ne pouvez tenter ce Test qu'une seule fois par artefact touché."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Diceman",
|
"id": "Diceman",
|
||||||
"name": "Maîtrise des dés",
|
"name": "Maîtrise des dés",
|
||||||
"description": "You are a dicing master, and all claims you cheat are clearly wrong. When you successfully use Gamble or Sleight of Hand when playing with dice, you can choose to either use your rolled SL, or the number rolled on your units die. So, a successful roll of 06 could be used for +6 SL. If you play any real-life dice games to represent in-game dice games, always roll extra dice equal to your Diceman level and choose the best results."
|
"description": "Vous êtes un maître des jeux de dés, et tous ceux qui affirment que vous trichez ont clairement tort. Quand vous utilisez Pari ou Escamotage de façon réussie avec des dés, vous pouvez choisir soit d'utiliser le DR obtenu, soit le chiffre des unités du dé. Donc, un lancer réussi de 06 peut être utilisé comme DR de +6. Si vous jouez à des jeux de dés réels pour représenter celui en jeu, lancez toujours un nombre de dés supplémentaires égal à votre niveau de Maîtrise des dés et choisissez les meilleurs résultats."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Dirty Fighting",
|
"id": "Dirty Fighting",
|
||||||
"name": "Combat déloyal",
|
"name": "Combat déloyal",
|
||||||
"description": "You have been taught all the dirty tricks of unarmed combat. You may choose to cause an extra +1 Damage for each level in Dirty Fighting with any successful Melee (Brawling) hit."
|
"description": "<p>Vous avez appris tous les coups bas du combat à mains nues. Vous pouvez choisir d'infliger +1 Dégât supplémentaire pour chaque niveau de Combat déloyal, pour tous les coups réussis avec Corps à corps (Bagarre).</p>\n<p> </p>\n<p>Remarque: utiliser ce Talent sera considéré comme une tricherie dans tout combat officiel.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Disarm",
|
"id": "Disarm",
|
||||||
"name": "Désarmer",
|
"name": "Désarmer",
|
||||||
"description": "<p>You are able to disarm an opponent with a careful flick of the wrist or a well-aimed blow to the hand. For your Action, you may attempt an <strong>Opposed Melee/Melee</strong> test. If you win, your opponent loses a held weapon, which flies 1d10 feet in a random direction (with further effects as determined by the GM). If you win by 2 SL, you can determine how far the weapon is flung instead of rolling randomly; if you win by 4 SL, you can also choose the direction the weapon goes in; if you win by 6 SL or more, you can take your opponent’s weapon if you have a free hand, plucking it from the air with a flourish. Tis Talent is of no use if your opponent has no weapon, or is a larger Size than you (see page 341).</p>"
|
"description": "<p>Vous êtes capable de désarmer un adversaire d'un coup de poignet judicieux ou grâce à une frappe bien ciblée à la main. Pour une Action, vous pouvez tenter un <strong>Test opposé Corps à corps/Corps à corps</strong>. Sur un succès, votre adversaire perd une arme tenue, qui atterit à 1d10 x 30 cm dans une direction aléatoire (avec des effets supplémentaires déterminées par le MJ). Si vous réussissez de 2 DR, vous pouvez déterminer à quelle distance l'arme est projetée au lieu d'effectuer un lancer ; si vous gagnez de 4 DR, vous pouvez également choisir la direction dans laquelle est projetée l'arme ; si vous gagnez de 6 DR ou plus, vous pouvez prendre l'arme de votre adversaire si vous avez une main libre, l'attrapant en l'air avec panache. Ce Talent est inutile si votre adversaire n'a pas d'arme, ou s'il est d'une Taille supérieure à la vôtre (voir page 342).</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Distract",
|
"id": "Distract",
|
||||||
"name": "Distraire",
|
"name": "Distraire",
|
||||||
"description": "<p>You are trained in simple movements to distract or startle your opponent, drawing eyes from your true intent. You may use your Move to perform a Distraction. This is resolved by an <strong>Opposed Athletics/Cool</strong> Test. If you win, your opponent can gain no Advantage until the end of the next Round.</p>"
|
"description": "<p>Vous êtes formé à effectuer des mouvements simples pour distraire ou surprendre votre adversaire, détournant les regards de votre véritable intention. Vous pouvez utiliser votre Mouvement pour Distraire. Ceci se résout par un <strong>Test opposé d'Athlétisme/Calme</strong>. Sur un succès, votre adversaire ne gagne pas d'Avantage jusqu'à la fin du prochain round.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Doomed",
|
"id": "Doomed",
|
||||||
"name": "Destinée",
|
"name": "Destinée",
|
||||||
"description": "At the age of 10, a Priest of Morr called a Doomsayer took you aside to foretell your death in an incense-laden, coming-of-age ritual called the Dooming. In conjunction with your GM, come up with a suitable Dooming. Should your character die in a fashion that matches your Dooming, your next character gains a bonus of half the total XP your dead character accrued during play."
|
"description": "À l'âge de 10 ans, un Prêtre de Morr aussi appelé Augure, vous a pris à part pour prédire votre mort lors d'un rituel de passage à l'âge adulte, lourd d'encens, appelé la Destinée. En accord avec votre MJ, proposez une Destinée appropriée. Si votre Personnage meurt d'une façon y correspondant, votre prochain Personnage gagnera un bonus égal à la moitié du total de PX accumulés par votre Personnage mort pendant la partie."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Drilled",
|
"id": "Drilled",
|
||||||
"name": "Coude-à_coude",
|
"name": "Coude-à_coude",
|
||||||
"description": "<p>You have been trained to fight shoulder-to-shoulder with other soldiers. If an enemy causes you to lose Advantage when standing beside an active ally with the Drilled Talent, you may keep 1 lost Advantage for each time you’ve taken the Drilled Talent.</p>"
|
"description": "<p>Vous avez été formé à combattre côte à côte avec d'autres soldats. Si un ennemi vous fait perdre des Avantages alors qu'il se tient à côté d 'un allié actif possédant le Talent Coude-à-coude, vous pouvez conserver 1 Avantage perdu pour chaque fois que vous avez pris le Talent Coude-à-coude.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Dual Wielder",
|
"id": "Dual Wielder",
|
||||||
"name": "Maniement de 2 armes",
|
"name": "Maniement de 2 armes",
|
||||||
"description": "<p>When armed with two weapons, you may attack with both for your Action. Roll to hit with the weapon held in your primary hand. If you hit, determine Damage as normal, but remember to keep your dice roll, as you will use it again. If the first strike hits, once it is resolved, the weapon in your secondary hand can then target an available opponent of your choice using the same dice roll for the first strike, but reversed. So, if you rolled 34 to hit with the first weapon, you use 43 to hit with the second. Remember to modify this second roll by your off-hand penalty (–20 unless you have the Ambidextrous Talent). This second attack is Opposed with a new defending roll, and damage for this second strike is calculated as normal. The only exception to this is if you roll a Critical for your first strike. If this happens, use the roll on the Critical Table to also act as the roll for the second attack. So, if you scored a critical to the head and rolled 56 on the Critical table for a Major Eye Wound, your second attack would then strike out with a to-hit value of 56. If you choose to attack with both weapons, all your defensive rolls until the start of your next Turn suffer a penalty of –10. You do not gain an Advantage when you successfully strike or Wound an opponent when Dual Wielding unless both attacks hit.</p>"
|
"description": "<p>Quand vous êtes équipé de deux armes, vous pouvez attaquer avec les deux pour votre Action. Effectuez un lancer pour toucher avec l'arme tenue par votre main principale. Si vous touchez, déterminez les Dégâts normalement, mais rappelez-vous de conserver votre lancer de dés, car vous l'utiliserez de nouveau. Si la première frappe est réussie, une fois résolue, l'arme dans votre main secondaire peut ensuite viser un adversaire disponible de votre choix en utilisant le même lancer de dés que celui de la première frappe, mais inversé. Donc, si vous avez obtenu 34 pour toucher avec la première arme, vous obtenez 43 pour toucher avec la seconde. Rappelez-vous de modifier ce second lancer avec la pénalité de votre mauvaise main (-20 à moins que vous ne possédiez le Talent Ambidextre). Cette seconde attaque est opposée à un nouveau lancer de défense et les Dégâts pour cette seconde frappe sont calculés normalement. Une exception à cela : si vous obtenez un Critique pour votre première frappe. Si cela se produit, utilisez le lancer du tableau des Critiques pour servir de lancer à la seconde attaque. Donc, si vous infligez un Critique à la tête et lancez un 56 sur le tableau des Critiques et obtenez une Blessure majeure à l'œil. Votre seconde attaque aurait donc une valeur de 56. Si vous choisissez d'attaquer avec les deux armes, tous vos lancers défensifs subissent une pénalité de -10 jusqu'au début de votre prochain Tour. Vous ne gagnez pas d'Avantage quand vous réussissez à frapper ou à infliger une Blessure à un adversaire avec le Maniement de deux armes sauf si les deux attaques touchent.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Embezzle",
|
"id": "Embezzle",
|
||||||
"name": "Escroqueur",
|
"name": "Escroqueur",
|
||||||
"description": "<p>You are skilled at skimming money from your employers without being detected. Whenever you secure money when Earning (during play or performing an Income Endeavour), you may attempt an <strong>Opposed Intelligence</strong> Test with your employer (assuming you have one). If you win, you skim 2d10 + SL brass pennies, 1d10 + SL silver shillings, or 1 + SL gold crowns (depending upon the size of the business in question, as determined by the GM) without being detected. If your employer wins by 6+ SL, you gain the money, but your embezzling is detected; what then happens is left to the GM. Any other result means you have failed to embezzle any money.</p>"
|
"description": "<p>Vous êtes doué pour détourner l'argent de vos employeurs sans vous faire prendre. Chaque fois que vous Gagnez de l'argent (pendant le jeu ou en effectuant une Activité Revenus), vous pouvez tenter un <strong>Test opposé d'intelligence</strong> avec votre employeur (en partant du principe que vous en avez un). Si vous gagnez, vous détournez 2d10 + DR sous de cuivre, 1d10 + DR pistoles d'argent, ou 1 + DR couronnes d'or (en fonction de la taille de la transaction en question, déterminée par le MJ) sans vous faire prendre. Si votre employeur gagne de +6 DR, vous gagnez votre argent, mais votre Escroquerie est repérée ; ce qui arrive ensuite est laissé à l'appréciation du MJ. Tout autre résultat signifie que vous avez échoué à escroquer de l'argent.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Enclosed Fighter",
|
"id": "Enclosed Fighter",
|
||||||
"name": "Combattant en espace clos",
|
"name": "Combattant en espace clos",
|
||||||
"description": "You have learned to make the most benefit out of fighting in enclosed spaces. You ignore penalties to Melee caused by confined spaces such as tunnels, the frontline, small fighting pits, and similar, and can use the Dodge Skill, even if it would normally be disallowed due to lack of space."
|
"description": "Vous avez appris à tirer le meilleur parti des combats en espace clos. Vous ignorez les pénalités de Corps à corps causées par les espaces confinés, comme les tunnels, les lignes de front, les petites fosses de combat et endroits similaires, et vous pouvez utiliser la Compétence Esquive, même si c'est normalement impossible à cause du manque d'espace."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Etiquette",
|
"id": "Etiquette",
|
||||||
"name": "Etiquette",
|
"name": "Savoir-vivre",
|
||||||
"description": "<p>You can blend in socially with the chosen group so long as you are dressed and acting appropriately. Example social groups for this Talent are: Criminals, Cultists, Guilders, Nobles, Scholars, Servants, and Soldiers. If you do not have the Talent, those with it will note your discomfort in the unfamiliar environment. This is primarily a matter for roleplaying, but may confer a bonus to Fellowship Tests at the GM’s discretion.</p>"
|
"description": "<p>Vous pouvez vous intégrer socialement au groupe choisi, tant que vous vous habillez et vous comportez de façon appropriée. Voici des exemples de groupes sociaux pour ce Talent : Criminels, Cultistes, Guildes, Nobles, Érudits, Serviteurs et Soldats. Si vous ne possédez pas le Talent, ceux qui le possèdent remarquent votre gêne dans un environnement non familier. Cela concerne principalement l'interprétation du Personnage, mais peut conférer un bonus aux Test de Sociabilité à la discrétion du MJ.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Fast Hands",
|
"id": "Fast Hands",
|
||||||
"name": "Mains agiles",
|
"name": "Mains agiles",
|
||||||
"description": "You can move your hands with surprising dexterity. Bystanders get no passive Perception Tests to spot your use of the Sleight of Hand Skill, instead they only get to Oppose your Sleight of Hand Tests if they actively suspect and are looking for your movements. Further, attempts to use Melee (Brawling) to simply touch an opponent gain a bonus of +10 <20> your level in Fast Hands."
|
"description": "Vous pouvez mouvoir vos mains avec une dextérité incroyable. Les passants ne bénéficient d'aucun Test de Perception passif pour remarquer que vous utilisez la Compétence Escamotage. Au lieu de cela, ils ne peuvent s'opposer à vos Tests d'Escamotage que qu'ils vous suspectent activement ou recherchent vos manipulations. De plus, les tentatives pour utiliser Corps à corps (Bagarre) pour simplement toucher un adversaire gagnent un bonus de +10 x votre Niveau de Mains Agiles."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Fast Shot",
|
"id": "Fast Shot",
|
||||||
"name": "Tir Rapide",
|
"name": "Tir Rapide",
|
||||||
"description": "If you have a loaded ranged weapon, you can fire it outside the normal Initiative Order before any other combatant reacts in the following Round. You roll to hit using all the normal modifiers. Employing Fast Shot requires both your Action and Move for your upcoming turn, and these will count as having been spent when your next turn arrives. If two or more characters use Fast Shot, the character who has taken this Talent most goes first. If any characters have taken Fast Shot an equal number of times, both shots are fired simultaneously, and should both be handled at the same time"
|
"description": "Si vous avez une arme à distance chargée, vous pouvez faire feu en dehors de l'ordre d'Initiative normal avant que tout autre combattant ne réagisse dans le Round suivant. Si l'arme est capable de tirs multiples, vous pouvez faire feu autant de fois que votre Niveau de Tir Rapide. Vous effectuez un lancer pour toucher en utilisant les modificateurs normaux. Utiliser Tir rapide nécessite à la fois votre Action et votre Mouvement pour votre tour à venir, qui compteront comme ayant été dépensés pendant le prochain tour. Si deux Personnages ou plus utilisent Tir Rapide, celui qui a pris ce Talent le plus de fois tire en premier. En cas d'égalité, ils font feu simultanément et devraient être gérés en même temps."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Fearless",
|
"id": "Fearless",
|
||||||
"name": "Sans peur",
|
"name": "Sans peur",
|
||||||
"description": "<p>You are either brave enough or crazy enough that fear of certain enemies has become a distant memory. With a single <strong>Average (+20) Cool</strong> Test, you may ignore any Intimidate, Fear, or Terror effects from the specified enemy when encountered. Typical enemies include Beastmen, Greenskins, Outlaws, Vampires, Watchmen, and Witches.</p>"
|
"description": "<p>Vous êtes suffisamment courageux ou fou pour que la peur de certains ennemis ne soit qu'un lointain souvenir. Avec un seul Test <strong>Calme Accessible (+20)</strong>, vous pouvez ignorez les effets d'Intimidation, de Peur ou de Terreur de l'ennemi spécifié quand vous le rencontrez. Les ennemis courants comprennent les hommes-bêtes, les peaux-vertes, les hors-la-loi, les vampires, les gardes et les sorciers sauvages.</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Feint",
|
"id": "Feint",
|
||||||
"name": "Feinte",
|
"name": "Feinte",
|
||||||
"description": "You have trained how to make false attacks in close combat to fool your opponent. You may now make a Feint for your Action against any opponent using a weapon. This is resolved with an Opposed Melee (Fencing)/Melee Test. If you win, and you attack the same opponent before the end of the next Round, you may add the SL of your Feint to your attack roll."
|
"description": "Vous avez appris comment faire de fausses attaques en combat rapproché pour duper votre adversaire. vous pouvez à présent faire un Feinte pour votre Action contre tout adversaire utilisant une arme. Cela est résolu avec un Test opposé Corps à Corps (Escrime)/Corps à corps. Si vous gagnez et que vous attaquez le même adversaire avant la fin du prochain Round, cette prochaine attaque ne peut être opposée par votre adversaire."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Field Dressing",
|
"id": "Field Dressing",
|
||||||
"name": "Pansement de fortune",
|
"name": "Pansement de fortune",
|
||||||
"description": "You are used to treating wounds quickly. If you fail a Heal Test when using Bandages, you may reverse the result if this will score a success; however, if you do so, you may not score more than +1 SL as you focus on speed over accuracy"
|
"description": "Vous êtes habitué à traiter rapidement les blessures. Si vous ratez un Test de Guérison quand vous utilisez des Bandages, vous pouvez inverser le résultat si cela entraîne un succès ; cependant, si vous le faites, vous ne pouvez pas obtenir plus de +1 DR, car vous vous concentrez sur la vitesse plutôt que sur la précision."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Fisherman",
|
"id": "Fisherman",
|
||||||
"name": "Pêcheur",
|
"name": "Pêcheur",
|
||||||
"description": "You are a very capable fisherman and know all the best ways to land fiseed yourself and a number of others equal to your level in Fisherman, assuming you choose to spend at least an hour or so with a line and bait. You may secure more fish in addition to this using the normal rules for foraging "
|
"description": "Vous êtes un pêcheur très habile et connaissez les meilleures façons d'attraper du poisson. En supposant qu'une étendue d'eau suffisamment grande soit disponible, vous êtes automatiquement considéré comme capable de pêcher suffisamment de poisson pour vous nourrir vous et un nombre de personnes égal à votre niveau de Pêcheur, en partant du principe que vous choisissez de passer au moins une heure ou deux avec une ligne et des appâts. Vous pouvez fournir plus de poisson en utilisant les règles normales pour la Recherche de nourriture."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Flagellant",
|
"id": "Flagellant",
|
||||||
"name": "Flagellant",
|
"name": "Flagellant",
|
||||||
"description": "You have dedicated your pain to the service of your God. Each day, you must spend half a bell (half an hour) praying as you maintain a number of Wounds su?ered equal to your level in Flagellant. Until you next sleep, if you have the Frenzy Talent you may enter Frenzy immediately without testing."
|
"description": "Vous avez voué votre douleur au service de votre Dieu. Chaque jour, vous devez passer une demi-cloche (une demi-heure) à prier en continuant à vous infliger un nombre de Blessures égal à votre Niveau de Flagellant. Après cela, si vous possédez le Talent Frénésie, vous pouvez entrer en Frénésie immédiatement sans effectuer de Test. Le Talent Frénésie est ajouté à la liste des Talents de n'importe quelle de vos Carrières. Si vous ne réussissez pas à vous flageller un jour donné, ou si vous permettez à votre chair meurtrie de guérir, vous ne pouvez dépenser aucune Résilience et aucune Détermination jusqu'à une nouvelle flagellation."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Flee!",
|
"id": "Flee!",
|
||||||
"name": "Fuite!",
|
"name": "Fuite!",
|
||||||
"description": "When your life is on the line you are capable of impressive bursts of speed. Your Movement Attribute counts as 1 higher when Fleeing."
|
"description": "Quand votre vie est en jeu, vous êtes capable de courir à une vitesse impressionnante. Votre Attribut de Mouvement compte comme s'il était augmenté de 1 quand vous fuyez."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Fleet Footed",
|
"id": "Fleet Footed",
|
||||||
"name": "Véloce",
|
"name": "Véloce",
|
||||||
"description": "You gain +1 to your Movement Attribute."
|
"description": "Vous gagnez +1 à votre Attribut de Mouvement."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Frenzy",
|
"id": "Frenzy",
|
||||||
"name": "Frénésie",
|
"name": "Frénésie",
|
||||||
"description": "<p style=\"user-select: text; color: #111111;\">With a Willpower Test, you can work yourself into a state of frenzy by psyching yourself up, howling, biting your shield, or similar. If you succeed, you become subject to Frenzy.</p>\n<p style=\"user-select: text; color: #111111;\"> </p>\n<p style=\"user-select: text; color: #111111;\">While subject to Frenzy you are immune to all other psychology, and will not flee or retreat for any reason; indeed you must always move at full rate towards the closest enemy you can see in order to attack. Generally, the only Action you may take is a Weapon Skill Test or an Athletics Test to reach an enemy more quickly. Further, you may take a Free Action Melee Test each Round as you are throwing everything you have into your attacks. Lastly, you gain a bonus of +1 Strength Bonus, such is your ferocity. You remain in Frenzy until all enemies in your line of sight are pacified, or you receive the <em style=\"user-select: text;\">Stunned</em> or <em style=\"user-select: text;\">Unconscious</em> condition. After your Frenzy is over you immediately receive a <em style=\"user-select: text;\">Fatigued</em> condition.</p>"
|
"description": "<p style=\"user-select: text; color: #111111;\">Vous pouvez entrer en Frénésie comme décrit à la page 190.</p>\n<p style=\"user-select: text; color: #111111;\"> </p>\n<p style=\"user-select: text; color: #111111;\">Tant que vous êtes en Frénésie, vous êtes immunisé à tous les autres Traits Psychologiques, et sous aucun prétexte vous ne fuirez, ni ne battrez en retraite. À l'inverse, vous devez vous déplacer à votre maximum en direction de l'ennemi le plus proche dans votre Ligne de Vue pour l'attaquer. La seule Action possible est un Test de CC ou un Test d'Athlétisme pour atteindre votre ennemi le plus raipdement possible. De plus, vous pouvez effectuer un Test de CC gratuit chaque round car vous vous lancez à corps perdu dans votre attaque. Enfin, vous gagnez un Bonus de Force +1 grâce à votre Férocité. Vous restez en Frénésie jusqu'à ce que tous les ennemis dans votre Ligne de Vue soient apaisés ou que vous gagniez l'État Assomé ou <em style=\"user-select: text;\">Stunned</em> or <em style=\"user-select: text;\">Inconscient</em> . Dès que votre Frénésie s'achève, vous gagnez l'État <em style=\"user-select: text;\">Exténué</em> .</p>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Frightening",
|
"id": "Frightening",
|
||||||
"name": "Effrayant",
|
"name": "Effrayant",
|
||||||
"description": "Anyone sane thinks twice before approaching you. If you wish, you have a Fear Rating of 1 . Add +1 to this number per extra time you have this Talent."
|
"description": "Toute personne saine d'esprit réfléchit à deux fois avant de vous approcher. Si vous le souhaitez, vous avez un indice de Peur de 1. Ajoutez +1 à cet indice par nombre de fois supplémentaires que vous avez pris ce Talent."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Furious Assault",
|
"id": "Furious Assault",
|
||||||
"name": "Assaut féroce",
|
"name": "Assaut féroce",
|
||||||
"description": "Your blows follow one another in quick succession, raining down on your opponents with the fury of Ulric. Once per Round, if you hit an opponent in close combat, you may immediately spend an Advantage or your Move to make an extra attack (assuming you have your Move remaining)."
|
"description": "Vos coups se succèdent rapidement et pleuvent sur vos adversaires avec la fureur d'Ulric. Une fois par round, si vous touchez un adversaire en combat rapproché, vous pouvez immédiatement dépenser un Avantage ou votre Mouvement pour effectuer une attaque supplémentaire (en supposant qu'ils vous reste votre Mouvement)."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Gregarious",
|
"id": "Gregarious",
|
||||||
"name": "Sociable",
|
"name": "Sociable",
|
||||||
"description": "You just like talking to other folk and it seems they like talking to you. You may reverse any failed Gossip Test if this allows the Test to succeed."
|
"description": "Vous aimez parler avec les autres et on dirait bien qu'ils aiment discuter avec vous. Vous pouvez inverser n'importe quel Test de Ragot si cela permet au Test de réussir."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Gunner",
|
"id": "Gunner",
|
||||||
|
1
tools/beasts.json
Normal file
1
tools/beasts.json
Normal file
File diff suppressed because one or more lines are too long
263
tools/compendium_wfrp4e.bestiary.json
Normal file
263
tools/compendium_wfrp4e.bestiary.json
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
{
|
||||||
|
"label": "Bestiaire",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"id": "Basilisk",
|
||||||
|
"name": "Basilic",
|
||||||
|
"description": "<p>Ces créatures reptiliennes à huit pattes sont solitaires et insaisissables. Considérées comme étant l'une des plus anciennes créatures du Chaos, on les dit emplies de tant de malveillance et de venin que même le sol sur lequel elles marchent est empoisonné. Leur morsure est également venimeuse, mais c'est leur regard pétrifiant qui est le plus à craindre. <br> Rarement rencontrés de nos jours, les basilics sont des ennemis des plus dangereux. Leurs glandes et leurs organes sont très prisés des Magiciens et Alchimistes. Ce qui fait que des chasseurs opportunistes écoutent souvent les rumeurs sur la présence de basilics dans les espaces sauvages du Vorbergland, et bien peu en reviennent.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Bear",
|
||||||
|
"name": "Ours",
|
||||||
|
"description": "<p>Arpentant les étendues sauvages du Reikland, les ours sont solitaires et généralement farouches, ne devenant agressifs que lorsque des intrus menacent leurs petits ou s'ils sont blessés. Quand la nourriture se raréfie, ils se rapprochent des villages non protégés et des voyageurs, en particulier ceux qui se montrent négligents avec les provisions.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Bloodletter of Khorne",
|
||||||
|
"name": "Sanguinaire de Khorne",
|
||||||
|
"description": "<p>Élus de Khorne, les sanguinaires règnent sur les champs de bataille du Vieux Monde, arrachant crânes et vies en l'honneur du Dieu Sang. Des dents pointues et acérées dépassent de leurs monstrueux visages cornus. Leur peau rouge-sang est dure comme l'airain, forgée sur l'enclume de guerres incessantes. Chaque sanguinaire porte une lame d'Enfer, diaboliquement tranchante et trempée dans le sang versé, qu'il manient avec témérité, se délectant du doux plaisir de la tuerie aveugle. </p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Boar",
|
||||||
|
"name": "Sanglier",
|
||||||
|
"description": "<p>A la recherche de nourriture dans la forêt, les sangliers sont des créatures solitaires, qui lorsqu'ils sont acculés, se battent en utilisant leurs défenses aiguisées avec une ténacité qui stupéfait ceux qui n'y sont pas préparés. Bien que la plupart mesurent entre 1m50 et 1m80 de long, certains atteignent des proportions remarquables. Les plus grands d'entre eux sont très prisés par les Orcs, qui les utilisent comme de féroces montures de guerre.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Bog Octopus",
|
||||||
|
"name": "Pieuvre des Tourbières",
|
||||||
|
"description": "<p>Les pieuvres des tourbières se cachent dans les eaux peu profondes, généralement dans les marécages. Elles y attendent leurs proies dans une immobilité parfaite, capables de sentir les vibrations des créatures en approche, puis surgissant de l'eau boueuse, cherchant à agripper et à noyer en utilisant leurs robustes tentacules. Généralement marbrées de vert et de brun, les pieuvres des tourbières bénéficient d'un camouflage parfaitement adapté aux environnements marécageux, leurs immenses yeux limpides étant souvent le seul indice trahissant leur présence. Beaucoup possèdent des tentacules de six mètres et plus et un corps de plus d'un mètre quatre-vingts de long, mais des histoires prétendent qu'elles atteignent plusieurs fois cette taille, en particulier si elles ont un apport régulier supplémentaire en viande.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Bray-Shaman",
|
||||||
|
"name": "Chaman-Bray",
|
||||||
|
"description": "<p>Les Chaman-Brays sont nés avec la capacité de manier les pouvoirs du Chaos, qu'ils utilisent avec un talent terrifiant. Ils n'ont jamais besoin de se défendre eux-même contre les autres membres de leur horde, ce qui est exceptionnel chez les hommes-bêtes. Personne n'oserait les attaquer puisqu'ils représentent la volonté des Sombres dieux en personne.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Cairn Wraith",
|
||||||
|
"name": "Spectre de Cairn",
|
||||||
|
"description": "<p>Les Spectres de Cairn sont des esprits particulièrement puissants, les restes spectraux d'aspirants nécromanciens qui ont cherché à prolonger leur existence grâce à la magie sombre. De leur vivant, ils étaient obstinés ; dans la mort, leur volonté malveillante les pousse à exercer une terrible vengeance sur les âmes ardentes des vivants. Beaucoup de ces Spectres hantent les cairns couverts de brume qui parsèment le paysage de l'Empire, tels que les Hägercrybs déchues.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Cave Squig",
|
||||||
|
"name": "Squig des Cavernes",
|
||||||
|
"description": "<p><Les squigs sont de grandes créatures fongoïdes, en général de forme rondes, qui vivent sous terre dans les grottes les plus profondes et les plus sombres. Ils arborent une gueule béante et de grandes dents pointues. Ils sont prisés des gobelins pour leur chair et leur peau, mais aussi comme gardes ou animaux de compagnie./p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Chaos Warrior",
|
||||||
|
"name": "Guerrier du Chaos",
|
||||||
|
"description": "<p>Énormes brutes lourdement cuirassées, affublées de pointes et d’impressionnants symboles de leurs dieux, les Guerriers du Chaos ne sont clairement plus des humains. Plus rien ne subsiste de leur existence passée, et ils n'existent que pour servir leur sombre chef et rien d'autre. Alors que la plupart des Guerriers du Chaos sont des maraudeurs de haut rang venant des régions loin au nord, quelques cultistes privilégiés peuvent se voir attribuer une armure du Chaos par les Sombres dieux, leur conférant une grande puissance, avec obligation de ne jamais l'ôter de leur vie. Comme peu de guerriers surpassent et qu'aucun chevalier n'est mieux protégé, leur espérance de vie demeure très longue.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Clanrat",
|
||||||
|
"name": "Guerrier des Clans",
|
||||||
|
"description": "<p>La plupart des Skavens sont originaires de l'un des nombreux et obscurs clans qui se chamaillent constamment pour des raisons diplomatiques, se poignardent dans le dos, ou encore déclarent la guerre aux autres. Ils agissent généralement sur les ordres de Skavens de statut supérieur, mais cherchent toujours un moyen d'obtenir une meilleure position, le plus souvent par trahison. Ils revêtent habituellement du cuir moisi ou des vêtements sales, avec des bouts de métal rouillé et terni leur servant d'armure. Les Guerriers des Clans sont souvent envoyés comme éclaireurs ou pillards pour récupérer des denrées, chercher des malepierres ou rafler des esclaves.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Crypt Ghoul",
|
||||||
|
"name": "Goule de Crypte",
|
||||||
|
"description": "<p>Parmi les créatures les plus pitoyables du Vieux Monde, les goules de cryptes sont des créatures voûtées et hideuses, à la peau cireuse et sale, et dont les dents jaunes bien aiguisées sont capables de déchirer la chair de leurs victimes. Les goules sont attirées par les énergies magiques de Shyish et de Dhar, ce qui signifie dans les faits qu'elles tournent autour vers des cimetières, des cryptes et des champs de bataille.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Cultist",
|
||||||
|
"name": "Cultiste",
|
||||||
|
"description": "<p>Pour ceux qui ne sont pas atteints par le fléau du Chaos, il ne représente qu'une abomination contre nature, mais pour ceux qu'il affecte, chaque pas vers la damnation, chaque idée les rapprochant du Chaos semble non seulement logique mais indispensable. Certains adeptes particulièrement dévoués des cultes proscrits reçoivent des \"dons\" de leurs dieux : d'immondes mutations qui les condamneront à mort si les Répurgateurs découvrent leur secret.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Daemonette of Slaanesh",
|
||||||
|
"name": "Démonette of Slaanesh",
|
||||||
|
"description": "<p>Comme toutes les créatures du Prince de la Souffrance et du Plaisir, les Démonettes de Slaanesh sont aussi belles qu'horribles. Leur allure surnaturelle défiant tout sens et toute rationalité, elles rendent leurs ennemis incapables de résister, tant ils sont charmés par la sensualité de leurs formes monstrueuses. Elles arborent une peau crémeuse et pâle et de grands yeux noirs comme du jais, ainsi qu'une épaisse chevelure flottant au vent et de couleur contre nature. Au lieu de mains, leurs bras sveltes se terminent par des griffes recourbées aux allures de pinces de crabe.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Demigryph",
|
||||||
|
"name": "Demigriffon",
|
||||||
|
"description": "<p>Avec leur tête d'aigle et leur corps de lion, les demigriffons sont de puissantes créatures au port altier. Ils arpentent les forêts et les prairies de l'Empire, généralement loin des habitations humaines, chassant seuls. Des demigriffons captifs sont utilisés par les Ordres de chevaliers les plus indomptables de l'Empire comme destrier de guerre. Contrairement aux plus grandes montures monstrueuses, généralement capturées jeunes et élevées en captivité, la capture d'un demigriffon adulte est un rite de passage dans certains ordres.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Dire Wolf",
|
||||||
|
"name": "Loup funeste",
|
||||||
|
"description": "<p>Quand la terre restitue les morts, il n'y a pas que les humains qui se relèvent : parodies macabres de loups géants aux yeux rougeoyants, aux lambeaux de chair tombant de leurs carcasses putréfiées, les Loups funestes rôdent la nuit pour le compte de leurs maîtres nécromanciens. Dans le Reikland, on dit qu'ils parcourent les forêts hantées des Hägercrybs lorsque Morrlieb est pleine, à la recherche d'une proies pour assouvir leur insatiable faim, rongés par un appétit de chair et un instinct accru pour l'odeur du sang.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Dog",
|
||||||
|
"name": "Chien",
|
||||||
|
"description": "<p>Les chiens sont élevés pour une myriade de raisons à travers le Vieux Monde. Alors que les toutous choyés des courtisans d'Altdorf ne sont une menace que pour la dignité d'un Joueur, les races plus grandes, y compris celles élevées pour la guerre et les combats de chiens, peuvent représenter une menace considérable.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Dragon",
|
||||||
|
"name": "Dragon",
|
||||||
|
"description": "<p>Les dragons régnaient sur les cieux bien avant que les anciennes races n'arpentent le Monde Connu. Même si les Dragons d'aujourd'hui ne sont plus que l'ombre de leurs frères, ils demeurent parmi les créatures connues les plus puissantes de l'Empire. Les quelques anciens Dragons survivants sont immenses et sortent rarement de leur léthargie. S'ils prennent ombrage de la présence d'un petit groupe d'aventuriers, ces derniers ne pourront pas faire grand-chose pour lui résister.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Fenbeast",
|
||||||
|
"name": "Bête des marais",
|
||||||
|
"description": "<p>Animés par les lanceurs de Sorts à partir d'immondices des marais et des tourbières, les Bêtes des Marais ne sont que des automates parfaitement stupides créés par magie. Vaguement humanoïdes, ils sont constitués de boue, d'os, de branches et de mucus, et nécéssitent d'importantes quantités de magie pour conserver leur intégrité. Parfois, ils sont animés par des Magicien spécialisés dans la Magie de Jade pour servir de gardes du corps ou pour accomplir une tâche spécifique nécessitant de la force brute ou un meurtre aveugle. Ils s'animent parfois de façon autonome, engendrés dans des bassins d'eau stagnante où le flux de Magie a été corrompu. Le Collège de Jade d'Altdorf conserve une vingtaine de ces créatures comme bêtes de somme, qui effectuent es corvées pour les Magisters supérieurs, maintenu par la focalisation incessante de douzaines d'apprentis.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Fimir",
|
||||||
|
"name": "Fimir",
|
||||||
|
"description": "<p>Les fimirs sont des créatures reptiliennes discrètes, pourvues d'un seul oeil. Ils chassent dans les recoins sombres des marais et des tourbières de l'ouest du Reikland, mais fuient la lumière du soleil, n'apparaissant habituellement qu'à l'aube ou au crépuscule, ou sous le couvert de la brume et du brouillard pour traquer des victimes à des fins mystérieuses. Des sorcières humaines inconscientes ou suffisamment désespérées pour se mêler des affaires des Démons ont été appelées pour rechercher un Fimirs dans le but de mettre au jour ses pernicieux mystères, car on prétend qu'elles invoquent et contrôlent ces entités. Et quand à savoir s'il est plus risqué de traiter avec les Puissances de la Ruine ou avec les Fimir, à chacun de le décider. Les Fimirs forment une société matriarcale. Le chef de chaque clan est une puissante sorcière appelée Meargh, aidée par un groupe de lanceurs de Sorts inférieurs appelé Dirach. La majorité des membres du clan sont de humbles Sheals, qui sont protégés par une caste de guerriers, les Fimms, au queues garnis de pointes et de boules osseues, utilisés pour briser les membres des adversaires imprudents.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Fr'hough Mournbreath",
|
||||||
|
"name": "Fr'hough Mournbreath"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Ghost",
|
||||||
|
"name": "Fantôme",
|
||||||
|
"description": "<p>Les fantômes sont les esprits d'âmes tourmentées, mortes alors que leurs affaires terrestres n'étaient pas réglées. Tout comme les squelettes et les zombies, les fantômes peuvent être invoqués par des vampires ou des nécromanciens, ou peuvent hanter des régions imprégnées de Dhar. Dans des cas exceptionnels, des esprits particulièrement déterminés peuvent se frayer un chemin depuis le royaume de Morr pour poursuivre leurs propres affaires, bien que de tels événements attirent rapidement l'attention du culte de Morr, ou des Magisters de l'Ordre de'Améthystes. Lorsqu'ils sont invoqués grâce aux arts nécromantiques, les fantômes se regroupent, formant de grandes armées d'esprits qui fondent sur leurs ennemis, répandant la peur et le désarroi dans leur passage.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Giant",
|
||||||
|
"name": "Géant",
|
||||||
|
"description": "<p>Les géants sont des créatures solitaires qui évitent généralement la civilisation. La plupart chassent dans les endroits éloignés et montagneux entourant l'Empire, se cachent dans des grottes et des ruines oubliées, loin des peuples plus petits, bien qu'ils migrent parfois dans les contreforts à la recherche de nourriture. Ils ont la réputation d'être belliqueux et féroces, à cause de leur taille impressionnante, de leur régime alimentaire à base de bétail et de leur association avec des armées de Peaux Vertes en maraude qui les asservissent. En réalité, beaucoup de géants ont un bon fond, sont en proie à des crises de colère et défendent farouchement leur vie privé, mais ne sont pas nécessairement hostiles aux autres.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Giant Rat",
|
||||||
|
"name": "Rat géant",
|
||||||
|
"description": "<p>Les rats s'infiltrent partout et sont particulièrement répandus dans les cités et les villes. Plus la population est dense, plus les rats s'entassent avec eux, ce qui est particulièrement néfaste quand les créatures sont porteuses de maladies. Généralement particulièrement petits, ils peuvent atteindre des proportions monstrueuses, et l'on rapporte la présence de rats sous Altdorf, de la taille d'un humain voire plus grands. On utilise des pièges à rats pour les garder sous contrôle.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Giant Spider",
|
||||||
|
"name": "Araignée Géante",
|
||||||
|
"description": "<p>Les araignées géantes se tapissent au plus profond des forêts et des grottes de l'Empire, mais elles peuvent vivre n'importe où, y compris dans les greniers poussiéreux et les caves sombres. La plupart capturent leurs proies à l'aide des toiles solides avant de leur injecter du venin. Bien que la plupart aient la taille d'un grand rat, certaines espèces d'araignées géantes sont effroyablement grandes. Les gobelins des forêts capturent souvent de grands spécimens pour en faire des montures.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Goblin",
|
||||||
|
"name": "Gobelin",
|
||||||
|
"description": "<p>Des gobelins, sire ! Par milliers!'</p>\n<p>- Lieutenant Bromkopf, 24ème régiment de Fantassins Reiklander</p>\n<p> </p>\n<p>L'instinct de conservation d'un gobelin, rachitique, malveillant, mais également agile et intelligent, ne doit jamais être sous-estimé. Ces créatures sont néanmoins lâches et se regroupent donc facilement si le nombre leur procure un avantage écrasant. Les gobelins rejoignent souvent les armées d'orcs - pas toujours par choix - s'appropriant opportunément une partie du butin de guerre pendant que les autres prennent part au plus gros des combats.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Gor",
|
||||||
|
"name": "Gor",
|
||||||
|
"description": "<p>Les Gors, les hommes-bêtes les plus courants, hantent presque chaque forêt du Vieux Monde. Leur apparence varie considérablement, mais tous allient des traits bestiaux et humains, souvent avec la tête et les jambes d'un bouc et le torse et les bras d'un humains. La seule caractéristique commune à tous les Gors est une grande paire de cornes - les plus grandes sont les meilleures, car elles indiquent leur statut parmi les hommes-bêtes - un trait qui les différencie des Ungor et des Changepeaux. Les plus grands Gors sont connus sous le nom de Bestigors.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Griffon",
|
||||||
|
"name": "Griffon",
|
||||||
|
"description": "<p>Avec les pattes avant et les ailes d'un aigle et l'arrière-train d'un grand chat, les Griffons sont des bêtes élégantes au port naturellement altier et majestueux. Ils nichent dans les montagnes qui entourent l'Empire et sont des tueurs rapides et efficaces, peu enclins à la destruction aveugle de créatures, contrairement aux manticores ou aux hippogriffes. Peut-être à cause de cette apparente noblesse, l'image du Griffon est extraordinairement populaire au sein de l'Empire, utilisée sur les blasons, dans l'iconographie religieuse et comme symbole de l'Empire lui-même. Les Griffons sont aussi l'une des bêtes les plus intelligentes. S'ils sont capturés relativement jeunes et soumis à un entraînement approprié, ils peuvent être extrêmement loyaux, capables d'anticiper et d'obéir à une grande variété d'ordres. Ils sont tellement prisés que les chasseurs meurent régulièrement en essayant de se procurer un œufs de Griffons pour le revendre. Le Griffon le plus célèbre du Reikland est sans doute Griffe-de-Mort. Installé dans le zoo impérial d'Altdorf, Griffe-de-Mort appartenait au prince héritier du Reikland et aurait parait-il éclos dans les mains de l'empereur Karl-Franz en personne - ce qui a causé l'impression de toutes sortes de dessins humoristiques dans des pamphlets séditieux.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Hippogryph",
|
||||||
|
"name": "Hippogryffe",
|
||||||
|
"description": "<p>Particulièrement féroces et territoriaux, les hippogriffes possèdent une tête et des ailes d'aigle, le buste d'un lion, et l'arrière-train d'un cheval. Issus le plus souvent des Montagnes Grises, ils attaquent sans provocation, presque imprudents dans leur rage, déchirant la chair en lambeaux pour la dévorer plus tard. Leur rage est telle que des hardes entiers de bétail peuvent être anéantis par un seul Hippogriffe, qui s'enfuit ensuite avec ses proies préférées, abandonnant les restes aux charognards. N'étant pas foncièrement intelligents, lors de la chasse, les hippogriffes ont tendance à tuer tout ce qu'ils voient. Supposant que tout ce qui ne voient plus disparaît à jamais, les victimes réelles des hippogriffes sont relativement peu nombreuses, puisqu'il suffit de trouver une bonne cachette pour leur échapper.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Horse",
|
||||||
|
"name": "Cheval",
|
||||||
|
"description": "<p>Les chevaux sont élevés pour différents usages, servant de monture rapide aux Messagers, de destriers robustes aux Chevaliers et de chevaux de traits pour les fermiers. Ils sont tellement utilisés que le commerce de chevaux est presque une compétition sportive au Reikland. Les maquignons (les vendeurs de chevaux) sans scrupules cherchent à se faire de l'argent sur le dos d'acheteurs ignorants, en lissant la robe et limant les dents des vieux chevaux, ou en leur bourrant les naseaux de petits chiffons pour éponger le mucus. Un acheteur non averti devrait donc se montrer prudent.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Hydra",
|
||||||
|
"name": "Hydre",
|
||||||
|
"description": "<p>L'Hydre possède plusieurs têtes semblables à celles d'un lézard et un corps massif qui supporte un entrelacs de cous et de têtes qui soufflent une fumée incandescente et mordent férocement. Créature étonnamment tenace et furtive, les hydres traquent leurs proies sur des kilomètres, mais elles perdent trop souvent patience et chargent, en rugissant de toutes leurs têtes.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Jabberslythe",
|
||||||
|
"name": "Jabberslythe",
|
||||||
|
"description": "<p>Le Jabberslythe est une ancienne créature du Chaos qui se cache dans les profondeurs des forêts les plus sombres. Créature démente, le Jabberslythe est un ignoble mélange de crapaud, de dragon de vase et d'insecte, le tout irrigués de sang noir corrosif qui jaillit à la moindre blessure. Pire encore, tous ceux qui regardent cette horreur perdent l'esprit, baragouinant, se griffant les yeux et riant hystériquement, faisant d'eux des proies facile pour le Jabberslythe. De plus, il dispose d'une langue collante capable de fouetter et de ramener son prochain repas dans sa gueule béante. En accord avec son apparence, la créature se déplace de façon gauche et balourde, et possède même des ailes, bien trop petites pour réussir à son impressionnant gabarit.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Manticore",
|
||||||
|
"name": "Manticore",
|
||||||
|
"description": "<p><em>\"Quand je voyageais avec les elfes d'Ulthuan, j'ai vu beaucoup de choses qui sidéreraient la plupart des gens. Une fois, j'ai aperçu une manticore, avec au beau milieu des sa crinière de lion un visage de haut elfe ! Je suppose que c'était moins une Manticore qu'une Elfticore.\"</em></p>\n<p>- Adhemar Fitztancred, Gardien Gris, conteur et menteur</p>\n<p> Heureusement assez rares, les Manticores sont féroces à l’extrême, animées par la volonté de débarrasser leur territoire, avec une extraordinaire brutalité, des prédateurs rivaux. Cela signifie que se trouver sur le territoire d'une Manticore ne passe pas inaperçu, car des corps d'autres monstres jonchent le sol. La créature possède la tête et le corps déformé d'un grand chat (bien que parfois son visage apparaisse un peu trop humain), les ailes d'une chauve-souris et une queue semblable à un fouet hérissé de barbelés.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Minotaur",
|
||||||
|
"name": "Minotaure",
|
||||||
|
"description": "<p>Les Minotaures semblables à des taureaux, énormes et massifs, dominent même les plus grands Bestigors. Les hordes possédant un grand nombre de Minotaures se considèrent comme particulièrement bénis par les Sombres Dieux. Les hommes-bêtes se rassemblent autour des Minotaures car leur présence imposante insuffle du courage même aux plus petits d'entre eux.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Mutant",
|
||||||
|
"name": "Mutant",
|
||||||
|
"description": "<p>L'un des destins les plus tragiques d'un Humain est de succomber à l'influence mutagène du Chaos. Cela peut se produire sans raison aucune, et même des bébés peuvent naître mutants. Lorsque cela se produit, beaucoup de parents ne trouvent pas la force d'assassiner leurs enfants, mais les abandonnent plutôt dans les bois, pour qu'ils meurent ou soient recueillis par d'autres mutants ou hommes-bêtes. Peu importe qu'ils soient innocents, tous les mutants sont une source de terreur pour le commun des mortels, et la plupart se tournent vers les Sombres dieux, abandonnés et aigris, ou en finissent avant qu'il ne soit trop tard.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Ogre",
|
||||||
|
"name": "Ogre",
|
||||||
|
"description": "<p><em><span class=\"fontstyle0\"><span class=\"fontstyle0\">'Oui, je les ai convoqués parce que j'avais besoin de renforts. Oui, je sais, ils mangent votre bétail. Et oui, je sais que les ogres ont un gros appétit. J'ai mis une annonce au poste de garde local, offrant une petite somme à toute personne ayant les moyens de les faire partir. Cela devrait être bien suffisant\". </span></span></em></p>\n<p><span class=\"fontstyle0\"><span class=\"fontstyle0\" style=\"font-size : 10pt ;\">- Augustus von Raushvel, Baron de Raush Vale</span> <br style=\"font-variant-numeric : normal ; font-variant-east-asian : normal ; hauteur de ligne : normal ; text-align : -webkit-auto ; text-size-adjust : auto ;\"></span></p>\n<p> </p>\n<p><span class=\"fontstyle0\">Grands, bruyants, brutaux et extrêmement violents, les ogres sont animés par le besoin de remplir leur énorme ventre musclé. Ils manquent d'astuce ou d'intelligence pour le faire avec subtilité, optant d'emblée pour la force dans la plupart des situations. Originaires des terres situées très à l'est, les Ogres sont répandus dans le Vieux Monde car ils adorent errer, toujours à la recherche de nouvelles proies. Au cours de leurs voyages à but alimentaires, longs de plusieurs décennies, ils mettent tout en oeuvre pour s'intégrer, portant des vêtements de la région et suivant les coutumes locales qu'ils comprennent, car cela leur permet d'attirer leur prochaine proie.</span></p>\n<p><span class=\"fontstyle0\"><br>Dans l'Empire, ils servent souvent dans l'Armée et sont souvent aperçus dans les grandes villes, où ils sont recrutés pour leurs muscles. La guilde des Vieux Halfling (des entrepreneurs en construction) s'est effectivement accaparé le travail bon marché des ogres dans nombre de villes et cités de l'Empire, source d'un grand mécontentement pour ses rivaux.</span></p>\n<p> </p>\n<h3>Halflings et ogres</h3>\n<p><span class=\"fontstyle0\">Personne ne sait pourquoi, mais les Halflings et les Ogres s'entendent bien. Beaucoup d'anciens des clans Halfling ont un ou deux gardes du corps ogres, et on dit que l'ancien de la Moot voyage rarement sans son vieil ami Zorarth Mord-Gigot, un ogre aux cheveux blancs qui vit dans l'empire depuis près d'un siècle. En retour, de nombreuses bandes de mercenaires ogres emploient des Halfling comme cuisiniers. Cependant, cette relation ne fonctionne pas toujours. Golgfag Mang'homme, le capitaine d'un groupe de mercenaire du nom de (quelle imagination !) Mang'hommes de Golgfag, employait notoirement un petit clan de Halflings aux cuisines, pour lui et ses hommes après les grandes victoires. Jusqu'à ce que Golgfag réalise que les cuisiniers avaient un bien meilleur goût que la nourriture qu'ils préparaient...</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Orc",
|
||||||
|
"name": "Orc",
|
||||||
|
"description": "<p>Nous êtres les meilleurs. Nous pas êtres des foutus Gobbos verts ou des trolls stupides, nous être plus fort ! Et si quelqu'un dit que nous pas fort, nous taper dur sur foutue tête.\n<p>- Gurkk Chopecrân', Chef Orc</p>\n<p> </p>\n<p>Les Orcs sont mauvais, brutaux, belliqueux et presque immunisés contre la douleur. Ils possèdent un corps massif et de larges épaules puissantes et ne laisseront pas une chose aussi insignifiante que la perte d'un bras faire obstacle à une bonne bagarre. Ils sont taillés pour le combat et n'aiment rien de plus au monde. Quand ils n'ont pas d'ennemis à combattre, ils s'en prennent à des groupes rivaux de Peaux-vertes. S'il n'y a pas de groupe rival, ils se battront entre eux. Bien que moins nombreux que les gobelins, ils sont plus grands et plus robustes, et toutes les occasions sont bonnes pour le faire savoir. Les Orcs peuvent atteindre des tailles prodigieuses, et les plus grands, les plus forts, les plus robustes et les plus agressifs d'entre eux se voient par conséquent accorder plus de prestige : la force entraîne le pouvoir dans leur société guerrière. Certains Orcs chevauchent d'énormes sangliers pendant les batailles, un spectacle assez terrifiant.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Pegasus",
|
||||||
|
"name": "Pégase",
|
||||||
|
"description": "<p>Les Pégases sont de magnifiques chevaux blancs pourvus d'ailes de cygne. Ce sont des voltigeurs acharnés et inépuisables qui se déplacent en grandes hordes dans les haute montagne, semblant prendre beaucoup de plaisir à virevolter dans les courants d'air chauds tourbillonnants. Les pégases font de parfaits destriers et beaucoup de guerriers ou d'éclaireurs ont tenté d'en capturer un. Ils sont très intelligents, et certains pensent qu'ils ne se laissent capturer que s'ils le veulent bien, ce qui a fait naître toutes sortes de légendes romantiques fantaisistes insistant sur le fait que seuls les plus dignes ou les plus vertueux pouvaient être choisis.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Pigeon",
|
||||||
|
"name": "Pigeon",
|
||||||
|
"description": "<p>Les pigeons sont élevés dans tout le Reikland pour transporter des messages de toutes sortes. Récemment, ils ont gagné en renommé pour le transport de chargements plus mortels, car l'oiseau facilement accessible est devenu populaire parmi les ingénieurs, qui les utilisent pour déployer leurs \"bombes-pigeons\" à des degrés divers de réussite.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Rat Ogre",
|
||||||
|
"name": "Rat Ogre",
|
||||||
|
"description": "<p>Les Rats Ogres sont des brutes imposantes, élevées dans les cavernes sombres de l'Empire souterrain par les Maîtres corrupteurs du Clan Molder. Ils sont stupides, mais intrépides et implacables au combat lorsqu'ils sont dirigés par leurs maîtres Skavens. Rarement rencontrés seuls, ils ont tendance à accompagner et servir de gardes du corps à des prophètes gris, ou autres Skavens de haut rang.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Skeleton",
|
||||||
|
"name": "Squelette",
|
||||||
|
"description": "<p>Les squelettes sont les restes décharnés de ceux qui sont morts depuis longtemps, réanimés, parodies de vivants, par une magie sombre pour arpenter la terre. Ceux qui sont morts sans recevoir le repos accordé par les rituels de Morr, le Dieu de la mort, peuvent être ressuscités sous cette forme par un nécromancien suffisamment puissant. Totalement dépourvus d'intelligence, les squelettes combattent jusqu'à ce que leurs os soient réduits en miettes. Leur ténacité n'a rien à voir avec du courage, ils n'ont tout simplement peur de rien et ne peuvent pas être tués puisqu'ils ne sont vivants.</p>\n<h3>Options : Des Os</h3>\n<p>Pour Refléter la nature osseuse des Squelettes, vous pouvez imposer une pénalité de -1 Dégâts aux blessures infligées par les armes n'ayant pas le trait Assommante.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Slenderthigh Whiptongue",
|
||||||
|
"name": "Slenderthigh Whiptongue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Snake",
|
||||||
|
"name": "Serpent",
|
||||||
|
"description": "<p>On trouve des serpents dans tout l'Empire, en particulier dans les forêts profondes. La plupart sont inoffensifs, mais certains utilisent un venin mortel ou enserrent leurs victimes jusqu'à ce qu'elles meurent par suffocation. Comme beaucoup de créatures du Vieux Monde, ils peuvent atteindre des proportions gigantesques, représentant un réel danger même pour le plus fort des mercenaires.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Snotling",
|
||||||
|
"name": "Snotling",
|
||||||
|
"description": "<p>Créatures écervelées, s'apparentant à des chiots excités et incontrôlables, les Snotlings sont des charognards et des imitateurs-nés, ramassant des os et des choses brillantes partout où ils passent, ou copiant les actions de tout ce qu'ils voient. Si les gobelins ou les orcs les poussent au conflits, ils se battent en nuées nauséabondes, essayant de submerger leurs ennemis par le poids du nombre. Pour cela, ils trouvent toutes sortes de substances répugnantes et toxiques, comme des champignons vénéneux et des déjections, à lancer sur leurs ennemis.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Stormvermin",
|
||||||
|
"name": "Vermine de choc",
|
||||||
|
"description": "<p>Les combattants d'élite des Skavens sont les Vermines de choc : plus grands, plus forts, plus endurcis et plus disciplinés que les Guerrier des clans. Ils servent de base à tout assaut majeur et constituent les gardes du corps de Skavens importants. Les Vermines de chocs sont généralement lourdement et cuirassées, arborant les combinaisons d'armes favorites de leur clan.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Tomb Banshee",
|
||||||
|
"name": "Banshee",
|
||||||
|
"description": "<p>Les Banshees sont les vestiges spectraux de sorcières autrefois puissantes dont les esprits sont imprégnés de l'énergie fétide de Dhar. Leur après-vie sans repos est tourmentée par le chagrin et l'amertume, un vide béant dans leurs âmes qui les pousse à émettre des hurlements déchirants, assez terrifiants et puissants pour plonger dans la folie ceux qui l'entendent, ou même arrêter leur cœur.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Troll",
|
||||||
|
"name": "Troll",
|
||||||
|
"description": "<p>Les trolls sont des créatures sales et immondes qui infestent tous les coins du Vieux Monde. Prompts à s'adapter à leur environnement, quel que soit leur type, ils sont tous énormes et imposants. Sombres et mus par leurs pulsions alimentaires, ils aiment tout de même amasser des biens et leurs repaires peuvent renfermer des trésors composés d'objets utiles et de valeur... et des restes macabres de leurs repas. Bien qu'il existe beaucoup d'espèces de Trolls, ils partagent tous des traits communs : ils sont généralement extrêmement stupides, ce qui signifie que tout ennemi à l'esprit vif d'esprit peut avoir un avantage sur eux ; ils peuvent régénérer, ce qui les rend extrêmement difficiles à tuer ; et ils sont capables de régurgiter leur dernier repas à volonté, en vomissant de la bile âcre sur une distance particulièrement impressionnantes - bien qu'ils répugnent à le faire, car la faim les tenaille alors.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Ungor",
|
||||||
|
"name": "Ungor",
|
||||||
|
"description": "<p>Les Ungors possèdent des cornes vestigiales ou très courtes, et il sont donc à peine considérés comme des \"Gor\" par le reste de la horde. Certains possèdent même des visages semblables à ceux des humains, faisant d'eux des infiltrés efficace, mais aussi des cibles faciles aux moqueries. En effet, les Ungors sont durement traités par les Gors, et sont souvent chétifs et mal nourris par rapport à leurs frères aux plus grandes cornes, ce qui en fait des créatures aigries, enclines à reporter leur jalousie sur les autres.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Vampire",
|
||||||
|
"name": "Vampire",
|
||||||
|
"description": "<p>Les vampires se considèrent comme les rois de la nuit. Beaucoup peuvent passer pour des humains, certains opérant même durant de longues périodes parmi les vivants. Malgré leur apparence extérieure d'humains, aucun coeur ne bat sous leur peau pâle, et au lieu des faims des mortels, ils souffrent d'une soif incessante de sang. Tous les vampires du Vieux Monde sont en fin de compte tous issus de lignées anciennes, apparues il y a des millénaires au Sud. Beaucoup sont extrêmement fiers de leur héritage, des traits et traditions qui les différencient des autres. Les vampires de lignées différentes sont souvent de grands rivaux, mais ils sont assez intelligents pour se rassembler quand cela devient nécessaire pour faire face aux ennemis les plus importants.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Varghulf",
|
||||||
|
"name": "Chauve-Souris Vampire",
|
||||||
|
"description": "<p>La plupart des vampires dissimulent leur besoin de sang sous une apparence de civilité et de savoir-vivre, se définissant eux-même comme les aristocrates des morts-vivants. Cependant, certains fuient leur côté humain et embrassent la bête qui les habitent. Ces Chauve-Souris Vampires sont des créatures cruelles et sauvages, abandonnées à l'autosatisfaction animale. Elles se manifestent sous la forme de grandes bêtes aux allures de chauve-souris, se délectant de leur besoin primaire de sang.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Wolf",
|
||||||
|
"name": "Loup",
|
||||||
|
"description": "<p>Les loups chassent généralement en meute et ont la réputation d'être des chasseurs tenaces, poursuivant leurs proies sur des dizaines de kilomètres. Plusieurs espèces rôdent dans le Reikland, dont les redoutables loups géants, qui sont capturés et élevés par les gobelins qui les utilisent comme gardes ou montures.</p>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Wyvern",
|
||||||
|
"name": "Vouivre",
|
||||||
|
"description": "Bien que les incultes les confondent souvent à tort avec des dragons, au-delà de quelques similitudes superficielles, les vouivres nauséabondes n'ont rien de commun avec ces dignes créatures. Charognards lâches et malvoyants, elles ont tendance à assouvir leur énorme appétit en choisissant des créatures sans défense - principalement des moutons et des chèvres -, évitant autant que possible le combat direct. Contrairement aux autres monstres des Montagnes, les vouivres ne sont pas particulièrement territoriales, et se déplacent généralement lorsque leurs territoires de chasse sont envahis."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Zombie",
|
||||||
|
"name": "Zombie",
|
||||||
|
"description": "<p>Comme les squelettes, les zombies sont des créatures mortes-vivantes réanimées et maintenues \"en vie\" par d'ignobles magies. Contrairement aux Squelettes, ils sont morts si récemment qu'une grande partie de leur corps subsiste encore, la chair malade et pourrissante s'affaissant sur leurs organes boursouflés et envahis par les vers. Quand ils combattent, leur chair et leurs organes se détachent de leurs os, libérant des effluves nauséabonde et toxiques, suffisamment puissantes pour retourner l'estomac de quiconque, à part, peut être, les soldats les plus endurcis.</p>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
29
tools/foundry_wh4_bestiary_merge.lua
Normal file
29
tools/foundry_wh4_bestiary_merge.lua
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
package.path = package.path .. ";luajson/?.lua"
|
||||||
|
local JSON = require"json"
|
||||||
|
|
||||||
|
local beastref_f = "../compendium/wfrp4e.bestiary.json"
|
||||||
|
local beastdescr_f = "compendium_wfrp4e.bestiary.json"
|
||||||
|
|
||||||
|
local f1 = io.open(beastref_f)
|
||||||
|
local strjson = f1:read("*a")
|
||||||
|
local beastref = JSON.decode(strjson)
|
||||||
|
|
||||||
|
local f2 = io.open(beastdescr_f)
|
||||||
|
strjson = f2:read("*a")
|
||||||
|
f2:close()
|
||||||
|
local beastdescr = JSON.decode(strjson)
|
||||||
|
|
||||||
|
|
||||||
|
for _, beasttext in pairs(beastdescr.entries) do
|
||||||
|
for _, beastgood in pairs(beastref.entries) do
|
||||||
|
if beasttext.id == beastgood.id then
|
||||||
|
beastgood.description = beasttext.description
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local jsonout = JSON.encode( beastref )
|
||||||
|
local fout = io.open("beasts.json", "w+")
|
||||||
|
fout:write( jsonout )
|
||||||
|
fout:close()
|
625
tools/lpeg/lpcap.lua
Normal file
625
tools/lpeg/lpcap.lua
Normal file
@ -0,0 +1,625 @@
|
|||||||
|
--[[
|
||||||
|
LPEGLJ
|
||||||
|
lpcap.lua
|
||||||
|
Capture functions
|
||||||
|
Copyright (C) 2014 Rostislav Sacek.
|
||||||
|
based on LPeg v1.0 - PEG pattern matching for Lua
|
||||||
|
Lua.org & PUC-Rio written by Roberto Ierusalimschy
|
||||||
|
http://www.inf.puc-rio.br/~roberto/lpeg/
|
||||||
|
|
||||||
|
** Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
** a copy of this software and associated documentation files (the
|
||||||
|
** "Software"), to deal in the Software without restriction, including
|
||||||
|
** without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
** distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
** permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
** the following conditions:
|
||||||
|
**
|
||||||
|
** The above copyright notice and this permission notice shall be
|
||||||
|
** included in all copies or substantial portions of the Software.
|
||||||
|
**
|
||||||
|
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
**
|
||||||
|
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
|
||||||
|
--]]
|
||||||
|
local ffi = require "ffi"
|
||||||
|
|
||||||
|
local Cclose = 0
|
||||||
|
local Cposition = 1
|
||||||
|
local Cconst = 2
|
||||||
|
local Cbackref = 3
|
||||||
|
local Carg = 4
|
||||||
|
local Csimple = 5
|
||||||
|
local Ctable = 6
|
||||||
|
local Cfunction = 7
|
||||||
|
local Cquery = 8
|
||||||
|
local Cstring = 9
|
||||||
|
local Cnum = 10
|
||||||
|
local Csubst = 11
|
||||||
|
local Cfold = 12
|
||||||
|
local Cruntime = 13
|
||||||
|
local Cgroup = 14
|
||||||
|
|
||||||
|
local MAXSTRCAPS = 10
|
||||||
|
|
||||||
|
local pushcapture
|
||||||
|
local addonestring
|
||||||
|
|
||||||
|
|
||||||
|
-- Goes back in a list of captures looking for an open capture
|
||||||
|
-- corresponding to a close
|
||||||
|
|
||||||
|
local function findopen(cs, index)
|
||||||
|
local n = 0; -- number of closes waiting an open
|
||||||
|
while true do
|
||||||
|
index = index - 1
|
||||||
|
if cs.ocap[index].kind == Cclose then
|
||||||
|
n = n + 1 -- one more open to skip
|
||||||
|
elseif cs.ocap[index].siz == 0 then
|
||||||
|
if n == 0 then
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
n = n - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function checknextcap(cs, captop)
|
||||||
|
local cap = cs.cap;
|
||||||
|
-- not a single capture? ((cap)->siz != 0)
|
||||||
|
if cs.ocap[cap].siz == 0 then
|
||||||
|
local n = 0; -- number of opens waiting a close
|
||||||
|
-- look for corresponding close
|
||||||
|
while true do
|
||||||
|
cap = cap + 1
|
||||||
|
if cap > captop then return end
|
||||||
|
if cs.ocap[cap].kind == Cclose then
|
||||||
|
n = n - 1
|
||||||
|
if n + 1 == 0 then
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
elseif cs.ocap[cap].siz == 0 then
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cap = cap + 1; -- + 1 to skip last close (or entire single capture)
|
||||||
|
if cap > captop then return end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Go to the next capture
|
||||||
|
|
||||||
|
local function nextcap(cs)
|
||||||
|
local cap = cs.cap;
|
||||||
|
-- not a single capture? ((cap)->siz != 0)
|
||||||
|
if cs.ocap[cap].siz == 0 then
|
||||||
|
local n = 0; -- number of opens waiting a close
|
||||||
|
-- look for corresponding close
|
||||||
|
while true do
|
||||||
|
cap = cap + 1
|
||||||
|
if cs.ocap[cap].kind == Cclose then
|
||||||
|
n = n - 1
|
||||||
|
if n + 1 == 0 then
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
elseif cs.ocap[cap].siz == 0 then
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cs.cap = cap + 1; -- + 1 to skip last close (or entire single capture)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Push on the Lua stack all values generated by nested captures inside
|
||||||
|
-- the current capture. Returns number of values pushed. 'addextra'
|
||||||
|
-- makes it push the entire match after all captured values. The
|
||||||
|
-- entire match is pushed also if there are no other nested values,
|
||||||
|
-- so the function never returns zero.
|
||||||
|
|
||||||
|
local function pushnestedvalues(cs, addextra, out, valuetable)
|
||||||
|
local co = cs.cap
|
||||||
|
cs.cap = cs.cap + 1
|
||||||
|
-- no nested captures?
|
||||||
|
if cs.ocap[cs.cap - 1].siz ~= 0 then
|
||||||
|
local st = cs.ocap[co].s
|
||||||
|
local l = cs.ocap[co].siz - 1
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = cs.s and cs.s:sub(st, st + l - 1) or cs.stream(st, st + l - 1)
|
||||||
|
return 1; -- that is it
|
||||||
|
else
|
||||||
|
local n = 0;
|
||||||
|
while cs.ocap[cs.cap].kind ~= Cclose do -- repeat for all nested patterns
|
||||||
|
n = n + pushcapture(cs, out, valuetable);
|
||||||
|
end
|
||||||
|
-- need extra?
|
||||||
|
if addextra or n == 0 then
|
||||||
|
local st = cs.ocap[co].s
|
||||||
|
local l = cs.ocap[cs.cap].s - cs.ocap[co].s
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = cs.s and cs.s:sub(st, st + l - 1) or cs.stream(st, st + l - 1)
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
cs.cap = cs.cap + 1 -- skip close entry
|
||||||
|
return n;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Push only the first value generated by nested captures
|
||||||
|
|
||||||
|
local function pushonenestedvalue(cs, out, valuetable)
|
||||||
|
local n = pushnestedvalues(cs, false, out, valuetable)
|
||||||
|
for i = n, 2, -1 do
|
||||||
|
out.out[out.outindex] = nil
|
||||||
|
out.outindex = out.outindex - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Try to find a named group capture with the name given at the top of
|
||||||
|
-- the stack; goes backward from 'cap'.
|
||||||
|
|
||||||
|
local function findback(cs, cap, name, valuetable)
|
||||||
|
-- repeat until end of list
|
||||||
|
while cap > 0 do
|
||||||
|
cap = cap - 1
|
||||||
|
local continue
|
||||||
|
if cs.ocap[cap].kind == Cclose then
|
||||||
|
cap = findopen(cs, cap); -- skip nested captures
|
||||||
|
elseif cs.ocap[cap].siz == 0 then
|
||||||
|
continue = true -- opening an enclosing capture: skip and get previous
|
||||||
|
end
|
||||||
|
if not continue and cs.ocap[cap].kind == Cgroup and cs.ocap[cap].idx ~= 0 then
|
||||||
|
local gname = valuetable[cs.ocap[cap].idx] -- get group name
|
||||||
|
-- right group?
|
||||||
|
if name == gname then
|
||||||
|
return cap;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
error(("back reference '%s' not found"):format(name), 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Back-reference capture. Return number of values pushed.
|
||||||
|
|
||||||
|
local function backrefcap(cs, out, valuetable)
|
||||||
|
local curr = cs.cap;
|
||||||
|
local name = valuetable[cs.ocap[cs.cap].idx] -- reference name
|
||||||
|
cs.cap = findback(cs, curr, name, valuetable) -- find corresponding group
|
||||||
|
local n = pushnestedvalues(cs, false, out, valuetable); -- push group's values
|
||||||
|
cs.cap = curr + 1;
|
||||||
|
return n;
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Table capture: creates a new table and populates it with nested
|
||||||
|
-- captures.
|
||||||
|
|
||||||
|
local function tablecap(cs, out, valuetable)
|
||||||
|
local n = 0;
|
||||||
|
local t = {}
|
||||||
|
cs.cap = cs.cap + 1
|
||||||
|
-- table is empty
|
||||||
|
if cs.ocap[cs.cap - 1].siz == 0 then
|
||||||
|
while cs.ocap[cs.cap].kind ~= Cclose do
|
||||||
|
local subout = { outindex = 0, out = {} }
|
||||||
|
-- named group?
|
||||||
|
if cs.ocap[cs.cap].kind == Cgroup and cs.ocap[cs.cap].idx ~= 0 then
|
||||||
|
local groupname = valuetable[cs.ocap[cs.cap].idx] -- push group name
|
||||||
|
pushonenestedvalue(cs, subout, valuetable)
|
||||||
|
t[groupname] = subout.out[1]
|
||||||
|
else
|
||||||
|
-- not a named group
|
||||||
|
local k = pushcapture(cs, subout, valuetable)
|
||||||
|
-- store all values into table
|
||||||
|
for i = 1, subout.outindex do
|
||||||
|
t[i + n] = subout.out[i]
|
||||||
|
end
|
||||||
|
n = n + k;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cs.cap = cs.cap + 1 -- skip close entry
|
||||||
|
end
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = t
|
||||||
|
return 1; -- number of values pushed (only the table)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Table-query capture
|
||||||
|
|
||||||
|
local function querycap(cs, out, valuetable)
|
||||||
|
local table = valuetable[cs.ocap[cs.cap].idx]
|
||||||
|
local subout = { outindex = 0, out = {} }
|
||||||
|
pushonenestedvalue(cs, subout, valuetable) -- get nested capture
|
||||||
|
-- query cap. value at table
|
||||||
|
if table[subout.out[1]] ~= nil then
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = table[subout.out[1]]
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Fold capture
|
||||||
|
|
||||||
|
local function foldcap(cs, out, valuetable)
|
||||||
|
local fce = valuetable[cs.ocap[cs.cap].idx]
|
||||||
|
cs.cap = cs.cap + 1
|
||||||
|
-- no nested captures?
|
||||||
|
-- or no nested captures (large subject)?
|
||||||
|
if cs.ocap[cs.cap - 1].siz ~= 0 or
|
||||||
|
cs.ocap[cs.cap].kind == Cclose then
|
||||||
|
error("no initial value for fold capture", 0);
|
||||||
|
end
|
||||||
|
local subout = { outindex = 0; out = {} }
|
||||||
|
local n = pushcapture(cs, subout, valuetable) -- nested captures with no values?
|
||||||
|
if n == 0 then
|
||||||
|
error("no initial value for fold capture", 0);
|
||||||
|
end
|
||||||
|
local acumulator = subout.out[1] -- leave only one result for accumulator
|
||||||
|
while cs.ocap[cs.cap].kind ~= Cclose do
|
||||||
|
local subout = { outindex = 0; out = {} }
|
||||||
|
n = pushcapture(cs, subout, valuetable); -- get next capture's values
|
||||||
|
acumulator = fce(acumulator, unpack(subout.out, 1, subout.outindex)) -- call folding function
|
||||||
|
end
|
||||||
|
cs.cap = cs.cap + 1; -- skip close entry
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = acumulator
|
||||||
|
return 1; -- only accumulator left on the stack
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function retcount(...)
|
||||||
|
return select('#', ...), { ... }
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Function capture
|
||||||
|
|
||||||
|
local function functioncap(cs, out, valuetable)
|
||||||
|
local fce = valuetable[cs.ocap[cs.cap].idx] -- push function
|
||||||
|
local subout = { outindex = 0, out = {} }
|
||||||
|
local n = pushnestedvalues(cs, false, subout, valuetable); -- push nested captures
|
||||||
|
local count, ret = retcount(fce(unpack(subout.out, 1, n))) -- call function
|
||||||
|
for i = 1, count do
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = ret[i]
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Select capture
|
||||||
|
|
||||||
|
local function numcap(cs, out, valuetable)
|
||||||
|
local idx = valuetable[cs.ocap[cs.cap].idx] -- value to select
|
||||||
|
-- no values?
|
||||||
|
if idx == 0 then
|
||||||
|
nextcap(cs); -- skip entire capture
|
||||||
|
return 0; -- no value produced
|
||||||
|
else
|
||||||
|
local subout = { outindex = 0, out = {} }
|
||||||
|
local n = pushnestedvalues(cs, false, subout, valuetable)
|
||||||
|
-- invalid index?
|
||||||
|
if n < idx then
|
||||||
|
error(("no capture '%d'"):format(idx), 0)
|
||||||
|
else
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = subout.out[idx] -- get selected capture
|
||||||
|
return 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Calls a runtime capture. Returns number of captures removed by
|
||||||
|
-- the call, including the initial Cgroup. (Captures to be added are
|
||||||
|
-- on the Lua stack.)
|
||||||
|
|
||||||
|
local function runtimecap(cs, close, s, out, valuetable)
|
||||||
|
local open = findopen(cs, close)
|
||||||
|
assert(cs.ocap[open].kind == Cgroup)
|
||||||
|
cs.ocap[close].kind = Cclose; -- closes the group
|
||||||
|
cs.ocap[close].s = s;
|
||||||
|
cs.cap = open;
|
||||||
|
local fce = valuetable[cs.ocap[cs.cap].idx] -- push function to be called
|
||||||
|
local subout = { outindex = 0, out = {} }
|
||||||
|
local n = pushnestedvalues(cs, false, subout, valuetable); -- push nested captures
|
||||||
|
local count, ret = retcount(fce(cs.s or cs.stream, s, unpack(subout.out, 1, n))) -- call dynamic function
|
||||||
|
for i = 1, count do
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = ret[i]
|
||||||
|
end
|
||||||
|
return close - open -- number of captures of all kinds removed
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Collect values from current capture into array 'cps'. Current
|
||||||
|
-- capture must be Cstring (first call) or Csimple (recursive calls).
|
||||||
|
-- (In first call, fills %0 with whole match for Cstring.)
|
||||||
|
-- Returns number of elements in the array that were filled.
|
||||||
|
|
||||||
|
local function getstrcaps(cs, cps, n)
|
||||||
|
local k = n
|
||||||
|
n = n + 1
|
||||||
|
cps[k + 1].isstring = true; -- get string value
|
||||||
|
cps[k + 1].startstr = cs.ocap[cs.cap].s; -- starts here
|
||||||
|
cs.cap = cs.cap + 1
|
||||||
|
-- nested captures?
|
||||||
|
if cs.ocap[cs.cap - 1].siz == 0 then
|
||||||
|
-- traverse them
|
||||||
|
while cs.ocap[cs.cap].kind ~= Cclose do
|
||||||
|
-- too many captures?
|
||||||
|
if n >= MAXSTRCAPS then
|
||||||
|
nextcap(cs); -- skip extra captures (will not need them)
|
||||||
|
elseif cs.ocap[cs.cap].kind == Csimple then
|
||||||
|
-- string?
|
||||||
|
n = getstrcaps(cs, cps, n); -- put info. into array
|
||||||
|
else
|
||||||
|
cps[n + 1].isstring = false; -- not a string
|
||||||
|
cps[n + 1].origcap = cs.cap; -- keep original capture
|
||||||
|
nextcap(cs);
|
||||||
|
n = n + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cs.cap = cs.cap + 1 -- skip close
|
||||||
|
end
|
||||||
|
cps[k + 1].endstr = cs.ocap[cs.cap - 1].s + cs.ocap[cs.cap - 1].siz - 1 -- ends here
|
||||||
|
return n;
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- add next capture value (which should be a string) to buffer 'b'
|
||||||
|
|
||||||
|
-- String capture: add result to buffer 'b' (instead of pushing
|
||||||
|
-- it into the stack)
|
||||||
|
|
||||||
|
local function stringcap(cs, b, valuetable)
|
||||||
|
local cps = {}
|
||||||
|
for i = 1, MAXSTRCAPS do
|
||||||
|
cps[#cps + 1] = {}
|
||||||
|
end
|
||||||
|
local fmt = valuetable[cs.ocap[cs.cap].idx]
|
||||||
|
local n = getstrcaps(cs, cps, 0) - 1; -- collect nested captures
|
||||||
|
local i = 1
|
||||||
|
-- traverse them
|
||||||
|
while i <= #fmt do
|
||||||
|
local c = fmt:sub(i, i)
|
||||||
|
-- not an escape?
|
||||||
|
if c ~= '%' then
|
||||||
|
b[#b + 1] = c -- add it to buffer
|
||||||
|
elseif fmt:sub(i + 1, i + 1) < '0' or fmt:sub(i + 1, i + 1) > '9' then
|
||||||
|
-- not followed by a digit?
|
||||||
|
i = i + 1
|
||||||
|
b[#b + 1] = fmt:sub(i, i)
|
||||||
|
else
|
||||||
|
i = i + 1
|
||||||
|
local l = fmt:sub(i, i) - '0'; -- capture index
|
||||||
|
if l > n then
|
||||||
|
error(("invalid capture index (%d)"):format(l), 0)
|
||||||
|
elseif cps[l + 1].isstring then
|
||||||
|
b[#b + 1] = cs.s and cs.s:sub(cps[l + 1].startstr, cps[l + 1].endstr - cps[l + 1].startstr + cps[l + 1].startstr - 1) or
|
||||||
|
cs.stream(cps[l + 1].startstr, cps[l + 1].endstr - cps[l + 1].startstr + cps[l + 1].startstr - 1)
|
||||||
|
else
|
||||||
|
local curr = cs.cap;
|
||||||
|
cs.cap = cps[l + 1].origcap; -- go back to evaluate that nested capture
|
||||||
|
if not addonestring(cs, b, "capture", valuetable) then
|
||||||
|
error(("no values in capture index %d"):format(l), 0)
|
||||||
|
end
|
||||||
|
cs.cap = curr; -- continue from where it stopped
|
||||||
|
end
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Substitution capture: add result to buffer 'b'
|
||||||
|
|
||||||
|
local function substcap(cs, b, valuetable)
|
||||||
|
local curr = cs.ocap[cs.cap].s;
|
||||||
|
-- no nested captures?
|
||||||
|
if cs.ocap[cs.cap].siz ~= 0 then
|
||||||
|
-- keep original text
|
||||||
|
b[#b + 1] = cs.s and cs.s:sub(curr, cs.ocap[cs.cap].siz - 1 + curr - 1) or
|
||||||
|
cs.stream(curr, cs.ocap[cs.cap].siz - 1 + curr - 1)
|
||||||
|
else
|
||||||
|
cs.cap = cs.cap + 1 -- skip open entry
|
||||||
|
-- traverse nested captures
|
||||||
|
while cs.ocap[cs.cap].kind ~= Cclose do
|
||||||
|
local next = cs.ocap[cs.cap].s;
|
||||||
|
b[#b + 1] = cs.s and cs.s:sub(curr, next - curr + curr - 1) or
|
||||||
|
cs.stream(curr, next - curr + curr - 1) -- add text up to capture
|
||||||
|
if addonestring(cs, b, "replacement", valuetable) then
|
||||||
|
curr = cs.ocap[cs.cap - 1].s + cs.ocap[cs.cap - 1].siz - 1; -- continue after match
|
||||||
|
else
|
||||||
|
-- no capture value
|
||||||
|
curr = next; -- keep original text in final result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
b[#b + 1] = cs.s and cs.s:sub(curr, curr + cs.ocap[cs.cap].s - curr - 1) or
|
||||||
|
cs.stream(curr, curr + cs.ocap[cs.cap].s - curr - 1) -- add last piece of text
|
||||||
|
end
|
||||||
|
cs.cap = cs.cap + 1 -- go to next capture
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Evaluates a capture and adds its first value to buffer 'b'; returns
|
||||||
|
-- whether there was a value
|
||||||
|
|
||||||
|
function addonestring(cs, b, what, valuetable)
|
||||||
|
local tag = cs.ocap[cs.cap].kind
|
||||||
|
if tag == Cstring then
|
||||||
|
stringcap(cs, b, valuetable); -- add capture directly to buffer
|
||||||
|
return 1
|
||||||
|
elseif tag == Csubst then
|
||||||
|
substcap(cs, b, valuetable); -- add capture directly to buffer
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
local subout = { outindex = 0, out = {} }
|
||||||
|
local n = pushcapture(cs, subout, valuetable);
|
||||||
|
if n > 0 then
|
||||||
|
if type(subout.out[1]) ~= 'string' and type(subout.out[1]) ~= 'number' then
|
||||||
|
error(("invalid %s value (a %s)"):format(what, type(subout.out[1])), 0)
|
||||||
|
end
|
||||||
|
b[#b + 1] = subout.out[1]
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Push all values of the current capture into the stack; returns
|
||||||
|
-- number of values pushed
|
||||||
|
|
||||||
|
function pushcapture(cs, out, valuetable)
|
||||||
|
local type = cs.ocap[cs.cap].kind
|
||||||
|
if type == Cposition then
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = cs.ocap[cs.cap].s
|
||||||
|
cs.cap = cs.cap + 1;
|
||||||
|
return 1;
|
||||||
|
elseif type == Cconst then
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = valuetable[cs.ocap[cs.cap].idx]
|
||||||
|
cs.cap = cs.cap + 1
|
||||||
|
return 1;
|
||||||
|
elseif type == Carg then
|
||||||
|
local arg = valuetable[cs.ocap[cs.cap].idx]
|
||||||
|
cs.cap = cs.cap + 1
|
||||||
|
if arg > cs.ptopcount then
|
||||||
|
error(("reference to absent extra argument #%d"):format(arg), 0)
|
||||||
|
end
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = cs.ptop[arg]
|
||||||
|
return 1;
|
||||||
|
elseif type == Csimple then
|
||||||
|
local k = pushnestedvalues(cs, true, out, valuetable)
|
||||||
|
local index = out.outindex
|
||||||
|
table.insert(out.out, index - k + 1, out.out[index])
|
||||||
|
out[index + 1] = nil
|
||||||
|
return k;
|
||||||
|
elseif type == Cruntime then
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = valuetable[cs.ocap[cs.cap].idx]
|
||||||
|
cs.cap = cs.cap + 1;
|
||||||
|
return 1;
|
||||||
|
elseif type == Cstring then
|
||||||
|
local b = {}
|
||||||
|
stringcap(cs, b, valuetable)
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = table.concat(b)
|
||||||
|
return 1;
|
||||||
|
elseif type == Csubst then
|
||||||
|
local b = {}
|
||||||
|
substcap(cs, b, valuetable);
|
||||||
|
out.outindex = out.outindex + 1
|
||||||
|
out.out[out.outindex] = table.concat(b)
|
||||||
|
return 1;
|
||||||
|
elseif type == Cgroup then
|
||||||
|
-- anonymous group?
|
||||||
|
if cs.ocap[cs.cap].idx == 0 then
|
||||||
|
return pushnestedvalues(cs, false, out, valuetable); -- add all nested values
|
||||||
|
else
|
||||||
|
-- named group: add no values
|
||||||
|
nextcap(cs); -- skip capture
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
elseif type == Cbackref then
|
||||||
|
return backrefcap(cs, out, valuetable)
|
||||||
|
elseif type == Ctable then
|
||||||
|
return tablecap(cs, out, valuetable)
|
||||||
|
elseif type == Cfunction then
|
||||||
|
return functioncap(cs, out, valuetable)
|
||||||
|
elseif type == Cnum then
|
||||||
|
return numcap(cs, out, valuetable)
|
||||||
|
elseif type == Cquery then
|
||||||
|
return querycap(cs, out, valuetable)
|
||||||
|
elseif type == Cfold then
|
||||||
|
return foldcap(cs, out, valuetable)
|
||||||
|
else
|
||||||
|
assert(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Prepare a CapState structure and traverse the entire list of
|
||||||
|
-- captures in the stack pushing its results. 's' is the subject
|
||||||
|
-- string, 'r' is the final position of the match, and 'ptop'
|
||||||
|
-- the index in the stack where some useful values were pushed.
|
||||||
|
-- Returns the number of results pushed. (If the list produces no
|
||||||
|
-- results, push the final position of the match.)
|
||||||
|
|
||||||
|
local function getcaptures(capture, s, stream, r, valuetable, ...)
|
||||||
|
local n = 0;
|
||||||
|
local cs = { cap = 0 }
|
||||||
|
local out = { outindex = 0; out = {} }
|
||||||
|
-- is there any capture?
|
||||||
|
if capture[cs.cap].kind ~= Cclose then
|
||||||
|
cs.ocap = capture
|
||||||
|
cs.s = s;
|
||||||
|
cs.stream = stream
|
||||||
|
cs.ptopcount, cs.ptop = retcount(...)
|
||||||
|
repeat -- collect their values
|
||||||
|
n = n + pushcapture(cs, out, valuetable)
|
||||||
|
until cs.ocap[cs.cap].kind == Cclose
|
||||||
|
end
|
||||||
|
-- no capture values?
|
||||||
|
if n == 0 then
|
||||||
|
if not r then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert(out.outindex < 7998, "(too many captures)")
|
||||||
|
return unpack(out.out, 1, out.outindex)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getcapturesruntime(capture, s, stream, notdelete, min, max, captop, valuetable, ...)
|
||||||
|
local n = 0;
|
||||||
|
local cs = { cap = min }
|
||||||
|
local out = { outindex = 0; out = {} }
|
||||||
|
cs.ocap = capture
|
||||||
|
cs.s = s
|
||||||
|
cs.stream = stream
|
||||||
|
cs.ptopcount, cs.ptop = retcount(...)
|
||||||
|
local start = 0
|
||||||
|
repeat -- collect their values
|
||||||
|
if not checknextcap(cs, max) then break end
|
||||||
|
local notdelete = notdelete or capture[cs.cap].kind == Cgroup and capture[cs.cap].idx ~= 0 and capture[cs.cap].candelete == 0
|
||||||
|
pushcapture(cs, out, valuetable)
|
||||||
|
if notdelete then
|
||||||
|
start = cs.cap
|
||||||
|
else
|
||||||
|
n = n + cs.cap - start
|
||||||
|
for i = 0, captop - cs.cap - 1 do
|
||||||
|
ffi.copy(capture + start + i, capture + cs.cap + i, ffi.sizeof('CAPTURE'))
|
||||||
|
end
|
||||||
|
max = max - (cs.cap - start)
|
||||||
|
captop = captop - (cs.cap - start)
|
||||||
|
cs.cap = start
|
||||||
|
end
|
||||||
|
until cs.cap == max
|
||||||
|
assert(out.outindex < 7998, "(too many captures)")
|
||||||
|
return n, out.out, out.outindex
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
getcaptures = getcaptures,
|
||||||
|
runtimecap = runtimecap,
|
||||||
|
getcapturesruntime = getcapturesruntime,
|
||||||
|
}
|
||||||
|
|
1057
tools/lpeg/lpcode.lua
Normal file
1057
tools/lpeg/lpcode.lua
Normal file
File diff suppressed because it is too large
Load Diff
1373
tools/lpeg/lpeg.lua
Normal file
1373
tools/lpeg/lpeg.lua
Normal file
File diff suppressed because it is too large
Load Diff
356
tools/lpeg/lpprint.lua
Normal file
356
tools/lpeg/lpprint.lua
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
--[[
|
||||||
|
LPEGLJ
|
||||||
|
lpprint.lua
|
||||||
|
Tree, code and debug print function (only for debuging)
|
||||||
|
Copyright (C) 2014 Rostislav Sacek.
|
||||||
|
based on LPeg v1.0 - PEG pattern matching for Lua
|
||||||
|
Lua.org & PUC-Rio written by Roberto Ierusalimschy
|
||||||
|
http://www.inf.puc-rio.br/~roberto/lpeg/
|
||||||
|
|
||||||
|
** Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
** a copy of this software and associated documentation files (the
|
||||||
|
** "Software"), to deal in the Software without restriction, including
|
||||||
|
** without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
** distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
** permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
** the following conditions:
|
||||||
|
**
|
||||||
|
** The above copyright notice and this permission notice shall be
|
||||||
|
** included in all copies or substantial portions of the Software.
|
||||||
|
**
|
||||||
|
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
**
|
||||||
|
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local ffi = require"ffi"
|
||||||
|
local band, rshift, lshift = bit.band, bit.rshift, bit.lshift
|
||||||
|
|
||||||
|
ffi.cdef[[
|
||||||
|
int isprint ( int c );
|
||||||
|
]]
|
||||||
|
|
||||||
|
local RuleLR = 0x10000
|
||||||
|
local Ruleused = 0x20000
|
||||||
|
|
||||||
|
-- {======================================================
|
||||||
|
-- Printing patterns (for debugging)
|
||||||
|
-- =======================================================
|
||||||
|
|
||||||
|
local TChar = 0
|
||||||
|
local TSet = 1
|
||||||
|
local TAny = 2 -- standard PEG elements
|
||||||
|
local TTrue = 3
|
||||||
|
local TFalse = 4
|
||||||
|
local TRep = 5
|
||||||
|
local TSeq = 6
|
||||||
|
local TChoice = 7
|
||||||
|
local TNot = 8
|
||||||
|
local TAnd = 9
|
||||||
|
local TCall = 10
|
||||||
|
local TOpenCall = 11
|
||||||
|
local TRule = 12 -- sib1 is rule's pattern, sib2 is 'next' rule
|
||||||
|
local TGrammar = 13 -- sib1 is initial (and first) rule
|
||||||
|
local TBehind = 14 -- match behind
|
||||||
|
local TCapture = 15 -- regular capture
|
||||||
|
local TRunTime = 16 -- run-time capture
|
||||||
|
|
||||||
|
local IAny = 0 -- if no char, fail
|
||||||
|
local IChar = 1 -- if char != aux, fail
|
||||||
|
local ISet = 2 -- if char not in val, fail
|
||||||
|
local ITestAny = 3 -- in no char, jump to 'offset'
|
||||||
|
local ITestChar = 4 -- if char != aux, jump to 'offset'
|
||||||
|
local ITestSet = 5 -- if char not in val, jump to 'offset'
|
||||||
|
local ISpan = 6 -- read a span of chars in val
|
||||||
|
local IBehind = 7 -- walk back 'aux' characters (fail if not possible)
|
||||||
|
local IRet = 8 -- return from a rule
|
||||||
|
local IEnd = 9 -- end of pattern
|
||||||
|
local IChoice = 10 -- stack a choice; next fail will jump to 'offset'
|
||||||
|
local IJmp = 11 -- jump to 'offset'
|
||||||
|
local ICall = 12 -- call rule at 'offset'
|
||||||
|
local IOpenCall = 13 -- call rule number 'offset' (must be closed to a ICall)
|
||||||
|
local ICommit = 14 -- pop choice and jump to 'offset'
|
||||||
|
local IPartialCommit = 15 -- update top choice to current position and jump
|
||||||
|
local IBackCommit = 16 -- "fails" but jump to its own 'offset'
|
||||||
|
local IFailTwice = 17 -- pop one choice and then fail
|
||||||
|
local IFail = 18 -- go back to saved state on choice and jump to saved offset
|
||||||
|
local IGiveup = 19 -- internal use
|
||||||
|
local IFullCapture = 20 -- complete capture of last 'off' chars
|
||||||
|
local IOpenCapture = 21 -- start a capture
|
||||||
|
local ICloseCapture = 22
|
||||||
|
local ICloseRunTime = 23
|
||||||
|
|
||||||
|
local Cclose = 0
|
||||||
|
local Cposition = 1
|
||||||
|
local Cconst = 2
|
||||||
|
local Cbackref = 3
|
||||||
|
local Carg = 4
|
||||||
|
local Csimple = 5
|
||||||
|
local Ctable = 6
|
||||||
|
local Cfunction = 7
|
||||||
|
local Cquery = 8
|
||||||
|
local Cstring = 9
|
||||||
|
local Cnum = 10
|
||||||
|
local Csubst = 11
|
||||||
|
local Cfold = 12
|
||||||
|
local Cruntime = 13
|
||||||
|
local Cgroup = 14
|
||||||
|
|
||||||
|
|
||||||
|
-- number of siblings for each tree
|
||||||
|
local numsiblings = {
|
||||||
|
[TRep] = 1,
|
||||||
|
[TSeq] = 2,
|
||||||
|
[TChoice] = 2,
|
||||||
|
[TNot] = 1,
|
||||||
|
[TAnd] = 1,
|
||||||
|
[TRule] = 2,
|
||||||
|
[TGrammar] = 1,
|
||||||
|
[TBehind] = 1,
|
||||||
|
[TCapture] = 1,
|
||||||
|
[TRunTime] = 1,
|
||||||
|
}
|
||||||
|
local names = {
|
||||||
|
[IAny] = "any",
|
||||||
|
[IChar] = "char",
|
||||||
|
[ISet] = "set",
|
||||||
|
[ITestAny] = "testany",
|
||||||
|
[ITestChar] = "testchar",
|
||||||
|
[ITestSet] = "testset",
|
||||||
|
[ISpan] = "span",
|
||||||
|
[IBehind] = "behind",
|
||||||
|
[IRet] = "ret",
|
||||||
|
[IEnd] = "end",
|
||||||
|
[IChoice] = "choice",
|
||||||
|
[IJmp] = "jmp",
|
||||||
|
[ICall] = "call",
|
||||||
|
[IOpenCall] = "open_call",
|
||||||
|
[ICommit] = "commit",
|
||||||
|
[IPartialCommit] = "partial_commit",
|
||||||
|
[IBackCommit] = "back_commit",
|
||||||
|
[IFailTwice] = "failtwice",
|
||||||
|
[IFail] = "fail",
|
||||||
|
[IGiveup] = "giveup",
|
||||||
|
[IFullCapture] = "fullcapture",
|
||||||
|
[IOpenCapture] = "opencapture",
|
||||||
|
[ICloseCapture] = "closecapture",
|
||||||
|
[ICloseRunTime] = "closeruntime"
|
||||||
|
}
|
||||||
|
|
||||||
|
local function printcharset(st)
|
||||||
|
io.write("[");
|
||||||
|
local i = 0
|
||||||
|
while i <= 255 do
|
||||||
|
local first = i;
|
||||||
|
while band(st[rshift(i, 5)], lshift(1, band(i, 31))) ~= 0 and i <= 255 do
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
if i - 1 == first then -- unary range?
|
||||||
|
io.write(("(%02x)"):format(first))
|
||||||
|
elseif i - 1 > first then -- non-empty range?
|
||||||
|
io.write(("(%02x-%02x)"):format(first, i - 1))
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
io.write("]")
|
||||||
|
end
|
||||||
|
|
||||||
|
local modes = {
|
||||||
|
[Cclose] = "close",
|
||||||
|
[Cposition] = "position",
|
||||||
|
[Cconst] = "constant",
|
||||||
|
[Cbackref] = "backref",
|
||||||
|
[Carg] = "argument",
|
||||||
|
[Csimple] = "simple",
|
||||||
|
[Ctable] = "table",
|
||||||
|
[Cfunction] = "function",
|
||||||
|
[Cquery] = "query",
|
||||||
|
[Cstring] = "string",
|
||||||
|
[Cnum] = "num",
|
||||||
|
[Csubst] = "substitution",
|
||||||
|
[Cfold] = "fold",
|
||||||
|
[Cruntime] = "runtime",
|
||||||
|
[Cgroup] = "group"
|
||||||
|
}
|
||||||
|
|
||||||
|
local function printcapkind(kind)
|
||||||
|
io.write(("%s"):format(modes[kind]))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printjmp(p, index)
|
||||||
|
io.write(("-> %d"):format(index + p[index].offset))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printrulename(p, index, rulenames)
|
||||||
|
if rulenames and rulenames[index + p[index].offset] then
|
||||||
|
io.write(' ', rulenames[index + p[index].offset])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printinst(p, index, valuetable, rulenames)
|
||||||
|
local code = p[index].code
|
||||||
|
if rulenames and rulenames[index] then
|
||||||
|
io.write(rulenames[index], '\n')
|
||||||
|
end
|
||||||
|
io.write(("%04d: %s "):format(index, names[code]))
|
||||||
|
if code == IChar then
|
||||||
|
io.write(("'%s'"):format(string.char(p[index].val)))
|
||||||
|
elseif code == ITestChar then
|
||||||
|
io.write(("'%s'"):format(string.char(p[index].val)))
|
||||||
|
printjmp(p, index)
|
||||||
|
printrulename(p, index, rulenames)
|
||||||
|
elseif code == IFullCapture then
|
||||||
|
printcapkind(band(p[index].val, 0x0f));
|
||||||
|
io.write((" (size = %d) (idx = %s)"):format(band(rshift(p[index].val, 4), 0xF), tostring(valuetable[p[index].offset])))
|
||||||
|
elseif code == IOpenCapture then
|
||||||
|
printcapkind(band(p[index].val, 0x0f))
|
||||||
|
io.write((" (idx = %s)"):format(tostring(valuetable[p[index].offset])))
|
||||||
|
elseif code == ISet then
|
||||||
|
printcharset(valuetable[p[index].val]);
|
||||||
|
elseif code == ITestSet then
|
||||||
|
printcharset(valuetable[p[index].val])
|
||||||
|
printjmp(p, index);
|
||||||
|
printrulename(p, index, rulenames)
|
||||||
|
elseif code == ISpan then
|
||||||
|
printcharset(valuetable[p[index].val]);
|
||||||
|
elseif code == IOpenCall then
|
||||||
|
io.write(("-> %d"):format(p[index].offset))
|
||||||
|
elseif code == IBehind then
|
||||||
|
io.write(("%d"):format(p[index].val))
|
||||||
|
elseif code == IJmp or code == ICall or code == ICommit or code == IChoice or
|
||||||
|
code == IPartialCommit or code == IBackCommit or code == ITestAny then
|
||||||
|
printjmp(p, index);
|
||||||
|
if (code == ICall or code == IJmp) and p[index].aux > 0 then
|
||||||
|
io.write(' ', valuetable[p[index].aux])
|
||||||
|
else
|
||||||
|
printrulename(p, index, rulenames)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function printpatt(p, valuetable)
|
||||||
|
local ruleNames = {}
|
||||||
|
for i = 0, p.size - 1 do
|
||||||
|
local code = p.p[i].code
|
||||||
|
if (code == ICall or code == IJmp) and p.p[i].aux > 0 then
|
||||||
|
local index = i + p.p[i].offset
|
||||||
|
ruleNames[index] = valuetable[p.p[i].aux]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i = 0, p.size - 1 do
|
||||||
|
printinst(p.p, i, valuetable, ruleNames)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function printcap(cap, index, valuetable)
|
||||||
|
printcapkind(cap[index].kind)
|
||||||
|
io.write((" (idx: %s - size: %d) -> %d\n"):format(valuetable[cap[index].idx], cap[index].siz, cap[index].s))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function printcaplist(cap, limit, valuetable)
|
||||||
|
io.write(">======\n")
|
||||||
|
local index = 0
|
||||||
|
while cap[index].s and index < limit do
|
||||||
|
printcap(cap, index, valuetable)
|
||||||
|
index = index + 1
|
||||||
|
end
|
||||||
|
io.write("=======\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ======================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- {======================================================
|
||||||
|
-- Printing trees (for debugging)
|
||||||
|
-- =======================================================
|
||||||
|
|
||||||
|
local tagnames = {
|
||||||
|
[TChar] = "char",
|
||||||
|
[TSet] = "set",
|
||||||
|
[TAny] = "any",
|
||||||
|
[TTrue] = "true",
|
||||||
|
[TFalse] = "false",
|
||||||
|
[TRep] = "rep",
|
||||||
|
[TSeq] = "seq",
|
||||||
|
[TChoice] = "choice",
|
||||||
|
[TNot] = "not",
|
||||||
|
[TAnd] = "and",
|
||||||
|
[TCall] = "call",
|
||||||
|
[TOpenCall] = "opencall",
|
||||||
|
[TRule] = "rule",
|
||||||
|
[TGrammar] = "grammar",
|
||||||
|
[TBehind] = "behind",
|
||||||
|
[TCapture] = "capture",
|
||||||
|
[TRunTime] = "run-time"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local function printtree(tree, ident, index, valuetable)
|
||||||
|
for i = 1, ident do
|
||||||
|
io.write(" ")
|
||||||
|
end
|
||||||
|
local tag = tree[index].tag
|
||||||
|
io.write(("%s"):format(tagnames[tag]))
|
||||||
|
if tag == TChar then
|
||||||
|
local c = tree[index].val
|
||||||
|
if ffi.C.isprint(c) then
|
||||||
|
io.write((" '%c'\n"):format(c))
|
||||||
|
else
|
||||||
|
io.write((" (%02X)\n"):format(c))
|
||||||
|
end
|
||||||
|
elseif tag == TSet then
|
||||||
|
printcharset(valuetable[tree[index].val]);
|
||||||
|
io.write("\n")
|
||||||
|
elseif tag == TOpenCall or tag == TCall then
|
||||||
|
io.write((" key: %s\n"):format(tostring(valuetable[tree[index].val])))
|
||||||
|
elseif tag == TBehind then
|
||||||
|
io.write((" %d\n"):format(tree[index].val))
|
||||||
|
printtree(tree, ident + 2, index + 1, valuetable);
|
||||||
|
elseif tag == TCapture then
|
||||||
|
io.write((" cap: %s n: %s\n"):format(modes[bit.band(tree[index].cap, 0xffff)], valuetable[tree[index].val]))
|
||||||
|
printtree(tree, ident + 2, index + 1, valuetable);
|
||||||
|
elseif tag == TRule then
|
||||||
|
local extra = bit.band(tree[index].cap, RuleLR) == RuleLR and ' left recursive' or ''
|
||||||
|
extra = extra .. (bit.band(tree[index].cap, Ruleused) ~= Ruleused and ' not used' or '')
|
||||||
|
io.write((" n: %d key: %s%s\n"):format(bit.band(tree[index].cap, 0xffff) - 1, valuetable[tree[index].val], extra))
|
||||||
|
printtree(tree, ident + 2, index + 1, valuetable);
|
||||||
|
-- do not print next rule as a sibling
|
||||||
|
elseif tag == TGrammar then
|
||||||
|
local ruleindex = index + 1
|
||||||
|
io.write((" %d\n"):format(tree[index].val)) -- number of rules
|
||||||
|
for i = 1, tree[index].val do
|
||||||
|
printtree(tree, ident + 2, ruleindex, valuetable);
|
||||||
|
ruleindex = ruleindex + tree[ruleindex].ps
|
||||||
|
end
|
||||||
|
assert(tree[ruleindex].tag == TTrue); -- sentinel
|
||||||
|
else
|
||||||
|
local sibs = numsiblings[tree[index].tag] or 0
|
||||||
|
io.write("\n")
|
||||||
|
if sibs >= 1 then
|
||||||
|
printtree(tree, ident + 2, index + 1, valuetable);
|
||||||
|
if sibs >= 2 then
|
||||||
|
printtree(tree, ident + 2, index + tree[index].ps, valuetable)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- }====================================================== */
|
||||||
|
|
||||||
|
return {
|
||||||
|
printtree = printtree,
|
||||||
|
printpatt = printpatt,
|
||||||
|
printcaplist = printcaplist,
|
||||||
|
printinst = printinst
|
||||||
|
}
|
1041
tools/lpeg/lpvm.lua
Normal file
1041
tools/lpeg/lpvm.lua
Normal file
File diff suppressed because it is too large
Load Diff
286
tools/lpeg/re.lua
Normal file
286
tools/lpeg/re.lua
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $
|
||||||
|
-- 2014/08/15 changes rostislav
|
||||||
|
|
||||||
|
-- imported functions and modules
|
||||||
|
local tonumber, print, error = tonumber, print, error
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local m = require"lpeglj"
|
||||||
|
|
||||||
|
-- 'm' will be used to parse expressions, and 'mm' will be used to
|
||||||
|
-- create expressions; that is, 're' runs on 'm', creating patterns
|
||||||
|
-- on 'mm'
|
||||||
|
local mm = m
|
||||||
|
|
||||||
|
-- pattern's metatable
|
||||||
|
local mt = getmetatable(mm.P(0))
|
||||||
|
mt = m.version() == "1.0.0.0LJ" and m or mt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- No more global accesses after this point
|
||||||
|
local version = _VERSION
|
||||||
|
if version == "Lua 5.2" then _ENV = nil end
|
||||||
|
|
||||||
|
|
||||||
|
local any = m.P(1)
|
||||||
|
|
||||||
|
|
||||||
|
-- Pre-defined names
|
||||||
|
local Predef = { nl = m.P"\n" }
|
||||||
|
|
||||||
|
|
||||||
|
local mem
|
||||||
|
local fmem
|
||||||
|
local gmem
|
||||||
|
|
||||||
|
|
||||||
|
local function updatelocale ()
|
||||||
|
mm.locale(Predef)
|
||||||
|
Predef.a = Predef.alpha
|
||||||
|
Predef.c = Predef.cntrl
|
||||||
|
Predef.d = Predef.digit
|
||||||
|
Predef.g = Predef.graph
|
||||||
|
Predef.l = Predef.lower
|
||||||
|
Predef.p = Predef.punct
|
||||||
|
Predef.s = Predef.space
|
||||||
|
Predef.u = Predef.upper
|
||||||
|
Predef.w = Predef.alnum
|
||||||
|
Predef.x = Predef.xdigit
|
||||||
|
Predef.A = any - Predef.a
|
||||||
|
Predef.C = any - Predef.c
|
||||||
|
Predef.D = any - Predef.d
|
||||||
|
Predef.G = any - Predef.g
|
||||||
|
Predef.L = any - Predef.l
|
||||||
|
Predef.P = any - Predef.p
|
||||||
|
Predef.S = any - Predef.s
|
||||||
|
Predef.U = any - Predef.u
|
||||||
|
Predef.W = any - Predef.w
|
||||||
|
Predef.X = any - Predef.x
|
||||||
|
mem = {} -- restart memoization
|
||||||
|
fmem = {}
|
||||||
|
gmem = {}
|
||||||
|
local mt = {__mode = "v"}
|
||||||
|
setmetatable(mem, mt)
|
||||||
|
setmetatable(fmem, mt)
|
||||||
|
setmetatable(gmem, mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
updatelocale()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end)
|
||||||
|
|
||||||
|
|
||||||
|
local function getdef (id, defs)
|
||||||
|
local c = defs and defs[id]
|
||||||
|
if not c then error("undefined name: " .. id) end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function patt_error (s, i)
|
||||||
|
local msg = (#s < i + 20) and s:sub(i)
|
||||||
|
or s:sub(i,i+20) .. "..."
|
||||||
|
msg = ("pattern error near '%s'"):format(msg)
|
||||||
|
error(msg, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function mult (p, n)
|
||||||
|
local np = mm.P(true)
|
||||||
|
while n >= 1 do
|
||||||
|
if n%2 >= 1 then np = np * p end
|
||||||
|
p = p * p
|
||||||
|
n = n/2
|
||||||
|
end
|
||||||
|
return np
|
||||||
|
end
|
||||||
|
|
||||||
|
local function equalcap (s, i, c)
|
||||||
|
if type(c) ~= "string" then return nil end
|
||||||
|
local e = #c + i
|
||||||
|
if type(s) == 'function' then -- stream mode
|
||||||
|
if s(i, e - 1) == c then return e else return nil end
|
||||||
|
else
|
||||||
|
if s:sub(i, e - 1) == c then return e else return nil end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
|
||||||
|
|
||||||
|
local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0
|
||||||
|
|
||||||
|
local arrow = S * "<-"
|
||||||
|
|
||||||
|
local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1
|
||||||
|
|
||||||
|
name = m.C(name)
|
||||||
|
|
||||||
|
|
||||||
|
-- a defined name only have meaning in a given environment
|
||||||
|
local Def = name * m.Carg(1)
|
||||||
|
|
||||||
|
local num = m.C(m.R"09"^1) * S / tonumber
|
||||||
|
|
||||||
|
local String = "'" * m.C((any - "'")^0) * "'" +
|
||||||
|
'"' * m.C((any - '"')^0) * '"'
|
||||||
|
|
||||||
|
|
||||||
|
local defined = "%" * Def / function (c,Defs)
|
||||||
|
local cat = Defs and Defs[c] or Predef[c]
|
||||||
|
if not cat then error ("name '" .. c .. "' undefined") end
|
||||||
|
return cat
|
||||||
|
end
|
||||||
|
|
||||||
|
local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
|
||||||
|
|
||||||
|
local item = defined + Range + m.C(any)
|
||||||
|
|
||||||
|
local Class =
|
||||||
|
"["
|
||||||
|
* (m.C(m.P"^"^-1)) -- optional complement symbol
|
||||||
|
* m.Cf(item * (item - "]")^0, mt.__add) /
|
||||||
|
function (c, p) return c == "^" and any - p or p end
|
||||||
|
* "]"
|
||||||
|
|
||||||
|
local function adddef (t, k, exp)
|
||||||
|
if t[k] then
|
||||||
|
error("'"..k.."' already defined as a rule")
|
||||||
|
else
|
||||||
|
t[k] = exp
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function firstdef (n, r) return adddef({n}, n, r) end
|
||||||
|
|
||||||
|
|
||||||
|
local function NT (n, b, p)
|
||||||
|
if not b then
|
||||||
|
error("rule '"..n.."' used outside a grammar")
|
||||||
|
else return mm.V(n, p or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local exp = m.P{ "Exp",
|
||||||
|
Exp = S * ( m.V"Grammar"
|
||||||
|
+ m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) );
|
||||||
|
Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul)
|
||||||
|
* (#seq_follow + patt_error);
|
||||||
|
Prefix = "&" * S * m.V"Prefix" / mt.__len
|
||||||
|
+ "!" * S * m.V"Prefix" / mt.__unm
|
||||||
|
+ m.V"Suffix";
|
||||||
|
Suffix = m.Cf(m.V"Primary" * S *
|
||||||
|
( ( m.P"+" * m.Cc(1, mt.__pow)
|
||||||
|
+ m.P"*" * m.Cc(0, mt.__pow)
|
||||||
|
+ m.P"?" * m.Cc(-1, mt.__pow)
|
||||||
|
+ "^" * ( m.Cg(num * m.Cc(mult))
|
||||||
|
+ m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow))
|
||||||
|
)
|
||||||
|
+ "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div))
|
||||||
|
+ m.P"{}" * m.Cc(nil, m.Ct)
|
||||||
|
+ m.Cg(Def / getdef * m.Cc(mt.__div))
|
||||||
|
)
|
||||||
|
+ "=>" * S * m.Cg(Def / getdef * m.Cc(m.Cmt))
|
||||||
|
) * S
|
||||||
|
)^0, function (a,b,f) return f(a,b) end );
|
||||||
|
Primary = "(" * m.V"Exp" * ")"
|
||||||
|
+ String / mm.P
|
||||||
|
+ Class
|
||||||
|
+ defined
|
||||||
|
+ "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" /
|
||||||
|
function (n, p) return mm.Cg(p, n) end
|
||||||
|
+ "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end
|
||||||
|
+ m.P"{}" / mm.Cp
|
||||||
|
+ "{~" * m.V"Exp" * "~}" / mm.Cs
|
||||||
|
+ "{|" * m.V"Exp" * "|}" / mm.Ct
|
||||||
|
+ "{" * m.V"Exp" * "}" / mm.C
|
||||||
|
+ m.P"." * m.Cc(any)
|
||||||
|
+ (name * m.Cb("G") * (S * ":" * S * num)^-1 * -arrow + "<" * name * m.Cb("G") * (S * ":" * S * num)^-1 * ">") / NT;
|
||||||
|
Definition = name * arrow * m.V"Exp";
|
||||||
|
Grammar = m.Cg(m.Cc(true), "G") *
|
||||||
|
m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0,
|
||||||
|
adddef) / mm.P
|
||||||
|
}
|
||||||
|
|
||||||
|
local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
|
||||||
|
|
||||||
|
|
||||||
|
local function compile (p, defs)
|
||||||
|
if mm.type(p) == "pattern" then return p end -- already compiled
|
||||||
|
local cp = pattern:match(p, 1, defs)
|
||||||
|
if not cp then error("incorrect pattern", 3) end
|
||||||
|
return cp
|
||||||
|
end
|
||||||
|
|
||||||
|
local function match (s, p, i)
|
||||||
|
local cp = mem[p]
|
||||||
|
if not cp then
|
||||||
|
cp = compile(p)
|
||||||
|
mem[p] = cp
|
||||||
|
end
|
||||||
|
return cp:match(s, i or 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function streammatch (p, i)
|
||||||
|
local cp = mem[p]
|
||||||
|
if not cp then
|
||||||
|
cp = compile(p)
|
||||||
|
mem[p] = cp
|
||||||
|
end
|
||||||
|
return cp:streammatch(i or 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Only for testing purpose
|
||||||
|
local function emulatestreammatch(s, p, i)
|
||||||
|
local cp = mem[p]
|
||||||
|
if not cp then
|
||||||
|
cp = compile(p)
|
||||||
|
mem[p] = cp
|
||||||
|
end
|
||||||
|
return cp:emulatestreammatch(s, i or 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find (s, p, i)
|
||||||
|
local cp = fmem[p]
|
||||||
|
if not cp then
|
||||||
|
cp = compile(p) / 0
|
||||||
|
cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
|
||||||
|
fmem[p] = cp
|
||||||
|
end
|
||||||
|
local i, e = cp:match(s, i or 1)
|
||||||
|
if i then return i, e - 1
|
||||||
|
else return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function gsub (s, p, rep)
|
||||||
|
local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
|
||||||
|
gmem[p] = g
|
||||||
|
local cp = g[rep]
|
||||||
|
if not cp then
|
||||||
|
cp = compile(p)
|
||||||
|
cp = mm.Cs((cp / rep + 1)^0)
|
||||||
|
g[rep] = cp
|
||||||
|
end
|
||||||
|
return cp:match(s)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- exported names
|
||||||
|
local re = {
|
||||||
|
compile = compile,
|
||||||
|
match = match,
|
||||||
|
streammatch = streammatch,
|
||||||
|
emulatestreammatch = emulatestreammatch,
|
||||||
|
find = find,
|
||||||
|
gsub = gsub,
|
||||||
|
updatelocale = updatelocale,
|
||||||
|
}
|
||||||
|
|
||||||
|
if version == "Lua 5.1" then _G.re = re end
|
||||||
|
|
||||||
|
return re
|
25
tools/luajson/json.lua
Normal file
25
tools/luajson/json.lua
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
package.path = package.path .. ";lpeg/?.lua"
|
||||||
|
local decode = require("json.decode")
|
||||||
|
local encode = require("json.encode")
|
||||||
|
local util = require("json.util")
|
||||||
|
|
||||||
|
local _G = _G
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local json = {
|
||||||
|
_VERSION = "1.3.4",
|
||||||
|
_DESCRIPTION = "LuaJSON : customizable JSON decoder/encoder",
|
||||||
|
_COPYRIGHT = "Copyright (c) 2007-2014 Thomas Harning Jr. <harningt@gmail.com>",
|
||||||
|
decode = decode,
|
||||||
|
encode = encode,
|
||||||
|
util = util
|
||||||
|
}
|
||||||
|
|
||||||
|
_G.json = json
|
||||||
|
|
||||||
|
return json
|
171
tools/luajson/json/decode.lua
Normal file
171
tools/luajson/json/decode.lua
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local lpeg = require("lpeg")
|
||||||
|
|
||||||
|
local error = error
|
||||||
|
local pcall = pcall
|
||||||
|
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local merge = jsonutil.merge
|
||||||
|
local util = require("json.decode.util")
|
||||||
|
|
||||||
|
local decode_state = require("json.decode.state")
|
||||||
|
|
||||||
|
local setmetatable, getmetatable = setmetatable, getmetatable
|
||||||
|
local assert = assert
|
||||||
|
local ipairs, pairs = ipairs, pairs
|
||||||
|
local string_char = require("string").char
|
||||||
|
|
||||||
|
local type = type
|
||||||
|
|
||||||
|
local require = require
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local modulesToLoad = {
|
||||||
|
"composite",
|
||||||
|
"strings",
|
||||||
|
"number",
|
||||||
|
"others"
|
||||||
|
}
|
||||||
|
local loadedModules = {
|
||||||
|
}
|
||||||
|
|
||||||
|
local json_decode = {}
|
||||||
|
|
||||||
|
json_decode.default = {
|
||||||
|
unicodeWhitespace = true,
|
||||||
|
initialObject = false,
|
||||||
|
nothrow = false
|
||||||
|
}
|
||||||
|
|
||||||
|
local modes_defined = { "default", "strict", "simple" }
|
||||||
|
|
||||||
|
json_decode.simple = {}
|
||||||
|
|
||||||
|
json_decode.strict = {
|
||||||
|
unicodeWhitespace = true,
|
||||||
|
initialObject = true,
|
||||||
|
nothrow = false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _,name in ipairs(modulesToLoad) do
|
||||||
|
local mod = require("json.decode." .. name)
|
||||||
|
if mod.mergeOptions then
|
||||||
|
for _, mode in pairs(modes_defined) do
|
||||||
|
mod.mergeOptions(json_decode[mode], mode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
loadedModules[#loadedModules + 1] = mod
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Shift over default into defaultOptions to permit build optimization
|
||||||
|
local defaultOptions = json_decode.default
|
||||||
|
json_decode.default = nil
|
||||||
|
|
||||||
|
local function generateDecoder(lexer, options)
|
||||||
|
-- Marker to permit detection of final end
|
||||||
|
local marker = {}
|
||||||
|
local parser = lpeg.Ct((options.ignored * lexer)^0 * lpeg.Cc(marker)) * options.ignored * (lpeg.P(-1) + util.unexpected())
|
||||||
|
local decoder = function(data)
|
||||||
|
local state = decode_state.create(options)
|
||||||
|
local parsed = parser:match(data)
|
||||||
|
assert(parsed, "Invalid JSON data")
|
||||||
|
local i = 0
|
||||||
|
while true do
|
||||||
|
i = i + 1
|
||||||
|
local item = parsed[i]
|
||||||
|
if item == marker then break end
|
||||||
|
if type(item) == 'function' and item ~= jsonutil.undefined and item ~= jsonutil.null then
|
||||||
|
item(state)
|
||||||
|
else
|
||||||
|
state:set_value(item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if options.initialObject then
|
||||||
|
assert(type(state.previous) == 'table', "Initial value not an object or array")
|
||||||
|
end
|
||||||
|
-- Make sure stack is empty
|
||||||
|
assert(state.i == 0, "Unclosed elements present")
|
||||||
|
return state.previous
|
||||||
|
end
|
||||||
|
if options.nothrow then
|
||||||
|
return function(data)
|
||||||
|
local status, rv = pcall(decoder, data)
|
||||||
|
if status then
|
||||||
|
return rv
|
||||||
|
else
|
||||||
|
return nil, rv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return decoder
|
||||||
|
end
|
||||||
|
|
||||||
|
local function buildDecoder(mode)
|
||||||
|
mode = mode and merge({}, defaultOptions, mode) or defaultOptions
|
||||||
|
for _, mod in ipairs(loadedModules) do
|
||||||
|
if mod.mergeOptions then
|
||||||
|
mod.mergeOptions(mode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local ignored = mode.unicodeWhitespace and util.unicode_ignored or util.ascii_ignored
|
||||||
|
-- Store 'ignored' in the global options table
|
||||||
|
mode.ignored = ignored
|
||||||
|
|
||||||
|
--local grammar = {
|
||||||
|
-- [1] = mode.initialObject and (ignored * (object_type + array_type)) or value_type
|
||||||
|
--}
|
||||||
|
local lexer
|
||||||
|
for _, mod in ipairs(loadedModules) do
|
||||||
|
local new_lexer = mod.generateLexer(mode)
|
||||||
|
lexer = lexer and lexer + new_lexer or new_lexer
|
||||||
|
end
|
||||||
|
return generateDecoder(lexer, mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Since 'default' is nil, we cannot take map it
|
||||||
|
local defaultDecoder = buildDecoder(json_decode.default)
|
||||||
|
local prebuilt_decoders = {}
|
||||||
|
for _, mode in pairs(modes_defined) do
|
||||||
|
if json_decode[mode] ~= nil then
|
||||||
|
prebuilt_decoders[json_decode[mode]] = buildDecoder(json_decode[mode])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Options:
|
||||||
|
number => number decode options
|
||||||
|
string => string decode options
|
||||||
|
array => array decode options
|
||||||
|
object => object decode options
|
||||||
|
initialObject => whether or not to require the initial object to be a table/array
|
||||||
|
allowUndefined => whether or not to allow undefined values
|
||||||
|
]]
|
||||||
|
local function getDecoder(mode)
|
||||||
|
mode = mode == true and json_decode.strict or mode or json_decode.default
|
||||||
|
local decoder = mode == nil and defaultDecoder or prebuilt_decoders[mode]
|
||||||
|
if decoder then
|
||||||
|
return decoder
|
||||||
|
end
|
||||||
|
return buildDecoder(mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function decode(data, mode)
|
||||||
|
local decoder = getDecoder(mode)
|
||||||
|
return decoder(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
local mt = {}
|
||||||
|
mt.__call = function(self, ...)
|
||||||
|
return decode(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
json_decode.getDecoder = getDecoder
|
||||||
|
json_decode.decode = decode
|
||||||
|
json_decode.util = util
|
||||||
|
setmetatable(json_decode, mt)
|
||||||
|
|
||||||
|
return json_decode
|
190
tools/luajson/json/decode/composite.lua
Normal file
190
tools/luajson/json/decode/composite.lua
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local pairs = pairs
|
||||||
|
local type = type
|
||||||
|
|
||||||
|
local lpeg = require("lpeg")
|
||||||
|
|
||||||
|
local util = require("json.decode.util")
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
|
||||||
|
local rawset = rawset
|
||||||
|
|
||||||
|
local assert = assert
|
||||||
|
local tostring = tostring
|
||||||
|
|
||||||
|
local error = error
|
||||||
|
local getmetatable = getmetatable
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
array = {
|
||||||
|
trailingComma = true
|
||||||
|
},
|
||||||
|
object = {
|
||||||
|
trailingComma = true,
|
||||||
|
number = true,
|
||||||
|
identifier = true,
|
||||||
|
setObjectKey = rawset
|
||||||
|
},
|
||||||
|
calls = {
|
||||||
|
defs = nil,
|
||||||
|
-- By default, do not allow undefined calls to be de-serialized as call objects
|
||||||
|
allowUndefined = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {
|
||||||
|
default = nil,
|
||||||
|
strict = {
|
||||||
|
array = {
|
||||||
|
trailingComma = false
|
||||||
|
},
|
||||||
|
object = {
|
||||||
|
trailingComma = false,
|
||||||
|
number = false,
|
||||||
|
identifier = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local function BEGIN_ARRAY(state)
|
||||||
|
state:push()
|
||||||
|
state:new_array()
|
||||||
|
end
|
||||||
|
local function END_ARRAY(state)
|
||||||
|
state:end_array()
|
||||||
|
state:pop()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function BEGIN_OBJECT(state)
|
||||||
|
state:push()
|
||||||
|
state:new_object()
|
||||||
|
end
|
||||||
|
local function END_OBJECT(state)
|
||||||
|
state:end_object()
|
||||||
|
state:pop()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function END_CALL(state)
|
||||||
|
state:end_call()
|
||||||
|
state:pop()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function SET_KEY(state)
|
||||||
|
state:set_key()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function NEXT_VALUE(state)
|
||||||
|
state:put_value()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, true, 'array', defaultOptions, mode and modeOptions[mode])
|
||||||
|
jsonutil.doOptionMerge(options, true, 'object', defaultOptions, mode and modeOptions[mode])
|
||||||
|
jsonutil.doOptionMerge(options, true, 'calls', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local isPattern
|
||||||
|
if lpeg.type then
|
||||||
|
function isPattern(value)
|
||||||
|
return lpeg.type(value) == 'pattern'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local metaAdd = getmetatable(lpeg.P("")).__add
|
||||||
|
function isPattern(value)
|
||||||
|
return getmetatable(value).__add == metaAdd
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function generateSingleCallLexer(name, func)
|
||||||
|
if type(name) ~= 'string' and not isPattern(name) then
|
||||||
|
error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern")
|
||||||
|
end
|
||||||
|
-- Allow boolean or function to match up w/ encoding permissions
|
||||||
|
if type(func) ~= 'boolean' and type(func) ~= 'function' then
|
||||||
|
error("Invalid functionCalls item: " .. name .. " not a function")
|
||||||
|
end
|
||||||
|
local function buildCallCapture(name)
|
||||||
|
return function(state)
|
||||||
|
if func == false then
|
||||||
|
error("Function call on '" .. name .. "' not permitted")
|
||||||
|
end
|
||||||
|
state:push()
|
||||||
|
state:new_call(name, func)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local nameCallCapture
|
||||||
|
if type(name) == 'string' then
|
||||||
|
nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name) / buildCallCapture
|
||||||
|
else
|
||||||
|
-- Name matcher expected to produce a capture
|
||||||
|
nameCallCapture = name * "(" / buildCallCapture
|
||||||
|
end
|
||||||
|
-- Call func over nameCallCapture and value to permit function receiving name
|
||||||
|
return nameCallCapture
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generateNamedCallLexers(options)
|
||||||
|
if not options.calls or not options.calls.defs then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local callCapture
|
||||||
|
for name, func in pairs(options.calls.defs) do
|
||||||
|
local newCapture = generateSingleCallLexer(name, func)
|
||||||
|
if not callCapture then
|
||||||
|
callCapture = newCapture
|
||||||
|
else
|
||||||
|
callCapture = callCapture + newCapture
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return callCapture
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generateCallLexer(options)
|
||||||
|
local lexer
|
||||||
|
local namedCapture = generateNamedCallLexers(options)
|
||||||
|
if options.calls and options.calls.allowUndefined then
|
||||||
|
lexer = generateSingleCallLexer(lpeg.C(util.identifier), true)
|
||||||
|
end
|
||||||
|
if namedCapture then
|
||||||
|
lexer = lexer and lexer + namedCapture or namedCapture
|
||||||
|
end
|
||||||
|
if lexer then
|
||||||
|
lexer = lexer + lpeg.P(")") * lpeg.Cc(END_CALL)
|
||||||
|
end
|
||||||
|
return lexer
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generateLexer(options)
|
||||||
|
local ignored = options.ignored
|
||||||
|
local array_options, object_options = options.array, options.object
|
||||||
|
local lexer =
|
||||||
|
lpeg.P("[") * lpeg.Cc(BEGIN_ARRAY)
|
||||||
|
+ lpeg.P("]") * lpeg.Cc(END_ARRAY)
|
||||||
|
+ lpeg.P("{") * lpeg.Cc(BEGIN_OBJECT)
|
||||||
|
+ lpeg.P("}") * lpeg.Cc(END_OBJECT)
|
||||||
|
+ lpeg.P(":") * lpeg.Cc(SET_KEY)
|
||||||
|
+ lpeg.P(",") * lpeg.Cc(NEXT_VALUE)
|
||||||
|
if object_options.identifier then
|
||||||
|
-- Add identifier match w/ validation check that it is in key
|
||||||
|
lexer = lexer + lpeg.C(util.identifier) * ignored * lpeg.P(":") * lpeg.Cc(SET_KEY)
|
||||||
|
end
|
||||||
|
local callLexers = generateCallLexer(options)
|
||||||
|
if callLexers then
|
||||||
|
lexer = lexer + callLexers
|
||||||
|
end
|
||||||
|
return lexer
|
||||||
|
end
|
||||||
|
|
||||||
|
local composite = {
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
generateLexer = generateLexer
|
||||||
|
}
|
||||||
|
|
||||||
|
return composite
|
100
tools/luajson/json/decode/number.lua
Normal file
100
tools/luajson/json/decode/number.lua
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local lpeg = require("lpeg")
|
||||||
|
local tonumber = tonumber
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local merge = jsonutil.merge
|
||||||
|
local util = require("json.decode.util")
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local digit = lpeg.R("09")
|
||||||
|
local digits = digit^1
|
||||||
|
|
||||||
|
-- Illegal octal declaration
|
||||||
|
local illegal_octal_detect = #(lpeg.P('0') * digits) * util.denied("Octal numbers")
|
||||||
|
|
||||||
|
local int = (lpeg.P('-') + 0) * (lpeg.R("19") * digits + illegal_octal_detect + digit)
|
||||||
|
|
||||||
|
local frac = lpeg.P('.') * digits
|
||||||
|
|
||||||
|
local exp = lpeg.S("Ee") * (lpeg.S("-+") + 0) * digits
|
||||||
|
|
||||||
|
local nan = lpeg.S("Nn") * lpeg.S("Aa") * lpeg.S("Nn")
|
||||||
|
local inf = lpeg.S("Ii") * lpeg.P("nfinity")
|
||||||
|
local ninf = lpeg.P('-') * lpeg.S("Ii") * lpeg.P("nfinity")
|
||||||
|
local hex = (lpeg.P("0x") + lpeg.P("0X")) * lpeg.R("09","AF","af")^1
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
nan = true,
|
||||||
|
inf = true,
|
||||||
|
frac = true,
|
||||||
|
exp = true,
|
||||||
|
hex = false
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {}
|
||||||
|
|
||||||
|
modeOptions.strict = {
|
||||||
|
nan = false,
|
||||||
|
inf = false
|
||||||
|
}
|
||||||
|
|
||||||
|
local nan_value = 0/0
|
||||||
|
local inf_value = 1/0
|
||||||
|
local ninf_value = -1/0
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Options: configuration options for number rules
|
||||||
|
nan: match NaN
|
||||||
|
inf: match Infinity
|
||||||
|
frac: match fraction portion (.0)
|
||||||
|
exp: match exponent portion (e1)
|
||||||
|
DEFAULT: nan, inf, frac, exp
|
||||||
|
]]
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generateLexer(options)
|
||||||
|
options = options.number
|
||||||
|
local ret = int
|
||||||
|
if options.frac then
|
||||||
|
ret = ret * (frac + 0)
|
||||||
|
else
|
||||||
|
ret = ret * (#frac * util.denied("Fractions", "number.frac") + 0)
|
||||||
|
end
|
||||||
|
if options.exp then
|
||||||
|
ret = ret * (exp + 0)
|
||||||
|
else
|
||||||
|
ret = ret * (#exp * util.denied("Exponents", "number.exp") + 0)
|
||||||
|
end
|
||||||
|
if options.hex then
|
||||||
|
ret = hex + ret
|
||||||
|
else
|
||||||
|
ret = #hex * util.denied("Hexadecimal", "number.hex") + ret
|
||||||
|
end
|
||||||
|
-- Capture number now
|
||||||
|
ret = ret / tonumber
|
||||||
|
if options.nan then
|
||||||
|
ret = ret + nan / function() return nan_value end
|
||||||
|
else
|
||||||
|
ret = ret + #nan * util.denied("NaN", "number.nan")
|
||||||
|
end
|
||||||
|
if options.inf then
|
||||||
|
ret = ret + ninf / function() return ninf_value end + inf / function() return inf_value end
|
||||||
|
else
|
||||||
|
ret = ret + (#ninf + #inf) * util.denied("+/-Inf", "number.inf")
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
local number = {
|
||||||
|
int = int,
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
generateLexer = generateLexer
|
||||||
|
}
|
||||||
|
|
||||||
|
return number
|
62
tools/luajson/json/decode/others.lua
Normal file
62
tools/luajson/json/decode/others.lua
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local lpeg = require("lpeg")
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local merge = jsonutil.merge
|
||||||
|
local util = require("json.decode.util")
|
||||||
|
|
||||||
|
-- Container module for other JavaScript types (bool, null, undefined)
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
-- For null and undefined, use the util.null value to preserve null-ness
|
||||||
|
local booleanCapture =
|
||||||
|
lpeg.P("true") * lpeg.Cc(true)
|
||||||
|
+ lpeg.P("false") * lpeg.Cc(false)
|
||||||
|
|
||||||
|
local nullCapture = lpeg.P("null")
|
||||||
|
local undefinedCapture = lpeg.P("undefined")
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
allowUndefined = true,
|
||||||
|
null = jsonutil.null,
|
||||||
|
undefined = jsonutil.undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {}
|
||||||
|
|
||||||
|
modeOptions.simple = {
|
||||||
|
null = false, -- Mapped to nil
|
||||||
|
undefined = false -- Mapped to nil
|
||||||
|
}
|
||||||
|
modeOptions.strict = {
|
||||||
|
allowUndefined = false
|
||||||
|
}
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generateLexer(options)
|
||||||
|
-- The 'or nil' clause allows false to map to a nil value since 'nil' cannot be merged
|
||||||
|
options = options.others
|
||||||
|
local valueCapture = (
|
||||||
|
booleanCapture
|
||||||
|
+ nullCapture * lpeg.Cc(options.null or nil)
|
||||||
|
)
|
||||||
|
if options.allowUndefined then
|
||||||
|
valueCapture = valueCapture + undefinedCapture * lpeg.Cc(options.undefined or nil)
|
||||||
|
else
|
||||||
|
valueCapture = valueCapture + #undefinedCapture * util.denied("undefined", "others.allowUndefined")
|
||||||
|
end
|
||||||
|
return valueCapture
|
||||||
|
end
|
||||||
|
|
||||||
|
local others = {
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
generateLexer = generateLexer
|
||||||
|
}
|
||||||
|
|
||||||
|
return others
|
189
tools/luajson/json/decode/state.lua
Normal file
189
tools/luajson/json/decode/state.lua
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local assert = assert
|
||||||
|
local type = type
|
||||||
|
local next = next
|
||||||
|
local unpack = require("table").unpack or unpack
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local state_ops = {}
|
||||||
|
local state_mt = {
|
||||||
|
__index = state_ops
|
||||||
|
}
|
||||||
|
|
||||||
|
function state_ops.pop(self)
|
||||||
|
self.previous_set = true
|
||||||
|
self.previous = self.active
|
||||||
|
local i = self.i
|
||||||
|
-- Load in this array into the active item
|
||||||
|
self.active = self.stack[i]
|
||||||
|
self.active_state = self.state_stack[i]
|
||||||
|
self.active_key = self.key_stack[i]
|
||||||
|
self.stack[i] = nil
|
||||||
|
self.state_stack[i] = nil
|
||||||
|
self.key_stack[i] = nil
|
||||||
|
|
||||||
|
self.i = i - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.push(self)
|
||||||
|
local i = self.i + 1
|
||||||
|
self.i = i
|
||||||
|
|
||||||
|
self.stack[i] = self.active
|
||||||
|
self.state_stack[i] = self.active_state
|
||||||
|
self.key_stack[i] = self.active_key
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.put_object_value(self, trailing)
|
||||||
|
local object_options = self.options.object
|
||||||
|
if trailing and object_options.trailingComma then
|
||||||
|
if not self.active_key then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert(self.active_key, "Missing key value")
|
||||||
|
object_options.setObjectKey(self.active, self.active_key, self:grab_value())
|
||||||
|
self.active_key = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.put_array_value(self, trailing)
|
||||||
|
-- Safety check
|
||||||
|
if trailing and not self.previous_set and self.options.array.trailingComma then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local new_index = self.active_state + 1
|
||||||
|
self.active_state = new_index
|
||||||
|
self.active[new_index] = self:grab_value()
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.put_value(self, trailing)
|
||||||
|
if self.active_state == 'object' then
|
||||||
|
self:put_object_value(trailing)
|
||||||
|
else
|
||||||
|
self:put_array_value(trailing)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.new_array(self)
|
||||||
|
local new_array = {}
|
||||||
|
if jsonutil.InitArray then
|
||||||
|
new_array = jsonutil.InitArray(new_array) or new_array
|
||||||
|
end
|
||||||
|
self.active = new_array
|
||||||
|
self.active_state = 0
|
||||||
|
self.active_key = nil
|
||||||
|
self:unset_value()
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.end_array(self)
|
||||||
|
if self.previous_set or self.active_state ~= 0 then
|
||||||
|
-- Not an empty array
|
||||||
|
self:put_value(true)
|
||||||
|
end
|
||||||
|
if self.active_state ~= #self.active then
|
||||||
|
-- Store the length in
|
||||||
|
self.active.n = self.active_state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.new_object(self)
|
||||||
|
local new_object = {}
|
||||||
|
self.active = new_object
|
||||||
|
self.active_state = 'object'
|
||||||
|
self.active_key = nil
|
||||||
|
self:unset_value()
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.end_object(self)
|
||||||
|
if self.previous_set or next(self.active) then
|
||||||
|
-- Not an empty object
|
||||||
|
self:put_value(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.new_call(self, name, func)
|
||||||
|
-- TODO setup properly
|
||||||
|
local new_call = {}
|
||||||
|
new_call.name = name
|
||||||
|
new_call.func = func
|
||||||
|
self.active = new_call
|
||||||
|
self.active_state = 0
|
||||||
|
self.active_key = nil
|
||||||
|
self:unset_value()
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.end_call(self)
|
||||||
|
if self.previous_set or self.active_state ~= 0 then
|
||||||
|
-- Not an empty array
|
||||||
|
self:put_value(true)
|
||||||
|
end
|
||||||
|
if self.active_state ~= #self.active then
|
||||||
|
-- Store the length in
|
||||||
|
self.active.n = self.active_state
|
||||||
|
end
|
||||||
|
local func = self.active.func
|
||||||
|
if func == true then
|
||||||
|
func = jsonutil.buildCall
|
||||||
|
end
|
||||||
|
self.active = func(self.active.name, unpack(self.active, 1, self.active.n or #self.active))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function state_ops.unset_value(self)
|
||||||
|
self.previous_set = false
|
||||||
|
self.previous = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.grab_value(self)
|
||||||
|
assert(self.previous_set, "Previous value not set")
|
||||||
|
self.previous_set = false
|
||||||
|
return self.previous
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.set_value(self, value)
|
||||||
|
assert(not self.previous_set, "Value set when one already in slot")
|
||||||
|
self.previous_set = true
|
||||||
|
self.previous = value
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_ops.set_key(self)
|
||||||
|
assert(self.active_state == 'object', "Cannot set key on array")
|
||||||
|
local value = self:grab_value()
|
||||||
|
local value_type = type(value)
|
||||||
|
if self.options.object.number then
|
||||||
|
assert(value_type == 'string' or value_type == 'number', "As configured, a key must be a number or string")
|
||||||
|
else
|
||||||
|
assert(value_type == 'string', "As configured, a key must be a string")
|
||||||
|
end
|
||||||
|
self.active_key = value
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function create(options)
|
||||||
|
local ret = {
|
||||||
|
options = options,
|
||||||
|
stack = {},
|
||||||
|
state_stack = {},
|
||||||
|
key_stack = {},
|
||||||
|
i = 0,
|
||||||
|
active = nil,
|
||||||
|
active_key = nil,
|
||||||
|
previous = nil,
|
||||||
|
active_state = nil
|
||||||
|
|
||||||
|
}
|
||||||
|
return setmetatable(ret, state_mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
local state = {
|
||||||
|
create = create
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
133
tools/luajson/json/decode/strings.lua
Normal file
133
tools/luajson/json/decode/strings.lua
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local lpeg = require("lpeg")
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local util = require("json.decode.util")
|
||||||
|
local merge = jsonutil.merge
|
||||||
|
|
||||||
|
local tonumber = tonumber
|
||||||
|
local string_char = require("string").char
|
||||||
|
local floor = require("math").floor
|
||||||
|
local table_concat = require("table").concat
|
||||||
|
|
||||||
|
local error = error
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local function get_error(item)
|
||||||
|
local fmt_string = item .. " in string [%q] @ %i:%i"
|
||||||
|
return lpeg.P(function(data, index)
|
||||||
|
local line, line_index, bad_char, last_line = util.get_invalid_character_info(data, index)
|
||||||
|
local err = fmt_string:format(bad_char, line, line_index)
|
||||||
|
error(err)
|
||||||
|
end) * 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local bad_unicode = get_error("Illegal unicode escape")
|
||||||
|
local bad_hex = get_error("Illegal hex escape")
|
||||||
|
local bad_character = get_error("Illegal character")
|
||||||
|
local bad_escape = get_error("Illegal escape")
|
||||||
|
|
||||||
|
local knownReplacements = {
|
||||||
|
["'"] = "'",
|
||||||
|
['"'] = '"',
|
||||||
|
['\\'] = '\\',
|
||||||
|
['/'] = '/',
|
||||||
|
b = '\b',
|
||||||
|
f = '\f',
|
||||||
|
n = '\n',
|
||||||
|
r = '\r',
|
||||||
|
t = '\t',
|
||||||
|
v = '\v',
|
||||||
|
z = '\z'
|
||||||
|
}
|
||||||
|
|
||||||
|
-- according to the table at http://da.wikipedia.org/wiki/UTF-8
|
||||||
|
local function utf8DecodeUnicode(code1, code2)
|
||||||
|
code1, code2 = tonumber(code1, 16), tonumber(code2, 16)
|
||||||
|
if code1 == 0 and code2 < 0x80 then
|
||||||
|
return string_char(code2)
|
||||||
|
end
|
||||||
|
if code1 < 0x08 then
|
||||||
|
return string_char(
|
||||||
|
0xC0 + code1 * 4 + floor(code2 / 64),
|
||||||
|
0x80 + code2 % 64)
|
||||||
|
end
|
||||||
|
return string_char(
|
||||||
|
0xE0 + floor(code1 / 16),
|
||||||
|
0x80 + (code1 % 16) * 4 + floor(code2 / 64),
|
||||||
|
0x80 + code2 % 64)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function decodeX(code)
|
||||||
|
code = tonumber(code, 16)
|
||||||
|
return string_char(code)
|
||||||
|
end
|
||||||
|
|
||||||
|
local doSimpleSub = lpeg.C(lpeg.S("'\"\\/bfnrtvz")) / knownReplacements
|
||||||
|
local doUniSub = lpeg.P('u') * (lpeg.C(util.hexpair) * lpeg.C(util.hexpair) + bad_unicode)
|
||||||
|
local doXSub = lpeg.P('x') * (lpeg.C(util.hexpair) + bad_hex)
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
badChars = '',
|
||||||
|
additionalEscapes = false, -- disallow untranslated escapes
|
||||||
|
escapeCheck = #lpeg.S('bfnrtv/\\"xu\'z'), -- no check on valid characters
|
||||||
|
decodeUnicode = utf8DecodeUnicode,
|
||||||
|
strict_quotes = false
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {}
|
||||||
|
|
||||||
|
modeOptions.strict = {
|
||||||
|
badChars = '\b\f\n\r\t\v',
|
||||||
|
additionalEscapes = false, -- no additional escapes
|
||||||
|
escapeCheck = #lpeg.S('bfnrtv/\\"u'), --only these chars are allowed to be escaped
|
||||||
|
strict_quotes = true
|
||||||
|
}
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function buildCaptureString(quote, badChars, escapeMatch)
|
||||||
|
local captureChar = (1 - lpeg.S("\\" .. badChars .. quote)) + (lpeg.P("\\") / "" * escapeMatch)
|
||||||
|
-- During error, force end
|
||||||
|
local captureString = captureChar^0 + (-#lpeg.P(quote) * bad_character + -1)
|
||||||
|
return lpeg.P(quote) * lpeg.Cs(captureString) * lpeg.P(quote)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generateLexer(options)
|
||||||
|
options = options.strings
|
||||||
|
local quotes = { '"' }
|
||||||
|
if not options.strict_quotes then
|
||||||
|
quotes[#quotes + 1] = "'"
|
||||||
|
end
|
||||||
|
local escapeMatch = doSimpleSub
|
||||||
|
escapeMatch = escapeMatch + doXSub / decodeX
|
||||||
|
escapeMatch = escapeMatch + doUniSub / options.decodeUnicode
|
||||||
|
if options.escapeCheck then
|
||||||
|
escapeMatch = options.escapeCheck * escapeMatch + bad_escape
|
||||||
|
end
|
||||||
|
if options.additionalEscapes then
|
||||||
|
escapeMatch = options.additionalEscapes + escapeMatch
|
||||||
|
end
|
||||||
|
local captureString
|
||||||
|
for i = 1, #quotes do
|
||||||
|
local cap = buildCaptureString(quotes[i], options.badChars, escapeMatch)
|
||||||
|
if captureString == nil then
|
||||||
|
captureString = cap
|
||||||
|
else
|
||||||
|
captureString = captureString + cap
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return captureString
|
||||||
|
end
|
||||||
|
|
||||||
|
local strings = {
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
generateLexer = generateLexer
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings
|
121
tools/luajson/json/decode/util.lua
Normal file
121
tools/luajson/json/decode/util.lua
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local lpeg = require("lpeg")
|
||||||
|
local select = select
|
||||||
|
local pairs, ipairs = pairs, ipairs
|
||||||
|
local tonumber = tonumber
|
||||||
|
local string_char = require("string").char
|
||||||
|
local rawset = rawset
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
|
||||||
|
local error = error
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
|
||||||
|
local table_concat = require("table").concat
|
||||||
|
|
||||||
|
local merge = require("json.util").merge
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local function get_invalid_character_info(input, index)
|
||||||
|
local parsed = input:sub(1, index)
|
||||||
|
local bad_character = input:sub(index, index)
|
||||||
|
local _, line_number = parsed:gsub('\n',{})
|
||||||
|
local last_line = parsed:match("\n([^\n]+.)$") or parsed
|
||||||
|
return line_number, #last_line, bad_character, last_line
|
||||||
|
end
|
||||||
|
|
||||||
|
local function build_report(msg)
|
||||||
|
local fmt = msg:gsub("%%", "%%%%") .. " @ character: %i %i:%i [%s] line:\n%s"
|
||||||
|
return lpeg.P(function(data, pos)
|
||||||
|
local line, line_index, bad_char, last_line = get_invalid_character_info(data, pos)
|
||||||
|
local text = fmt:format(pos, line, line_index, bad_char, last_line)
|
||||||
|
error(text)
|
||||||
|
end) * 1
|
||||||
|
end
|
||||||
|
local function unexpected()
|
||||||
|
local msg = "unexpected character"
|
||||||
|
return build_report(msg)
|
||||||
|
end
|
||||||
|
local function denied(item, option)
|
||||||
|
local msg
|
||||||
|
if option then
|
||||||
|
msg = ("'%s' denied by option set '%s'"):format(item, option)
|
||||||
|
else
|
||||||
|
msg = ("'%s' denied"):format(item)
|
||||||
|
end
|
||||||
|
return build_report(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 09, 0A, 0B, 0C, 0D, 20
|
||||||
|
local ascii_space = lpeg.S("\t\n\v\f\r ")
|
||||||
|
local unicode_space
|
||||||
|
do
|
||||||
|
local chr = string_char
|
||||||
|
local u_space = ascii_space
|
||||||
|
-- \u0085 \u00A0
|
||||||
|
u_space = u_space + lpeg.P(chr(0xC2)) * lpeg.S(chr(0x85) .. chr(0xA0))
|
||||||
|
-- \u1680 \u180E
|
||||||
|
u_space = u_space + lpeg.P(chr(0xE1)) * (lpeg.P(chr(0x9A, 0x80)) + chr(0xA0, 0x8E))
|
||||||
|
-- \u2000 - \u200A, also 200B
|
||||||
|
local spacing_end = ""
|
||||||
|
for i = 0x80,0x8b do
|
||||||
|
spacing_end = spacing_end .. chr(i)
|
||||||
|
end
|
||||||
|
-- \u2028 \u2029 \u202F
|
||||||
|
spacing_end = spacing_end .. chr(0xA8) .. chr(0xA9) .. chr(0xAF)
|
||||||
|
u_space = u_space + lpeg.P(chr(0xE2, 0x80)) * lpeg.S(spacing_end)
|
||||||
|
-- \u205F
|
||||||
|
u_space = u_space + lpeg.P(chr(0xE2, 0x81, 0x9F))
|
||||||
|
-- \u3000
|
||||||
|
u_space = u_space + lpeg.P(chr(0xE3, 0x80, 0x80))
|
||||||
|
-- BOM \uFEFF
|
||||||
|
u_space = u_space + lpeg.P(chr(0xEF, 0xBB, 0xBF))
|
||||||
|
unicode_space = u_space
|
||||||
|
end
|
||||||
|
|
||||||
|
local identifier = lpeg.R("AZ","az","__") * lpeg.R("AZ","az", "__", "09") ^0
|
||||||
|
|
||||||
|
local hex = lpeg.R("09","AF","af")
|
||||||
|
local hexpair = hex * hex
|
||||||
|
|
||||||
|
local comments = {
|
||||||
|
cpp = lpeg.P("//") * (1 - lpeg.P("\n"))^0 * lpeg.P("\n"),
|
||||||
|
c = lpeg.P("/*") * (1 - lpeg.P("*/"))^0 * lpeg.P("*/")
|
||||||
|
}
|
||||||
|
|
||||||
|
local comment = comments.cpp + comments.c
|
||||||
|
|
||||||
|
local ascii_ignored = (ascii_space + comment)^0
|
||||||
|
|
||||||
|
local unicode_ignored = (unicode_space + comment)^0
|
||||||
|
|
||||||
|
-- Parse the lpeg version skipping patch-values
|
||||||
|
-- LPEG <= 0.7 have no version value... so 0.7 is value
|
||||||
|
local DecimalLpegVersion = lpeg.version and tonumber(lpeg.version():match("^(%d+%.%d+)")) or 0.7
|
||||||
|
|
||||||
|
local function setObjectKeyForceNumber(t, key, value)
|
||||||
|
key = tonumber(key) or key
|
||||||
|
return rawset(t, key, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
local util = {
|
||||||
|
unexpected = unexpected,
|
||||||
|
denied = denied,
|
||||||
|
ascii_space = ascii_space,
|
||||||
|
unicode_space = unicode_space,
|
||||||
|
identifier = identifier,
|
||||||
|
hex = hex,
|
||||||
|
hexpair = hexpair,
|
||||||
|
comments = comments,
|
||||||
|
comment = comment,
|
||||||
|
ascii_ignored = ascii_ignored,
|
||||||
|
unicode_ignored = unicode_ignored,
|
||||||
|
DecimalLpegVersion = DecimalLpegVersion,
|
||||||
|
get_invalid_character_info = get_invalid_character_info,
|
||||||
|
setObjectKeyForceNumber = setObjectKeyForceNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
return util
|
161
tools/luajson/json/encode.lua
Normal file
161
tools/luajson/json/encode.lua
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local type = type
|
||||||
|
local assert, error = assert, error
|
||||||
|
local getmetatable, setmetatable = getmetatable, setmetatable
|
||||||
|
|
||||||
|
local ipairs, pairs = ipairs, pairs
|
||||||
|
local require = require
|
||||||
|
|
||||||
|
local output = require("json.encode.output")
|
||||||
|
|
||||||
|
local util = require("json.util")
|
||||||
|
local util_merge, isCall = util.merge, util.isCall
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
--[[
|
||||||
|
List of encoding modules to load.
|
||||||
|
Loaded in sequence such that earlier encoders get priority when
|
||||||
|
duplicate type-handlers exist.
|
||||||
|
]]
|
||||||
|
local modulesToLoad = {
|
||||||
|
"strings",
|
||||||
|
"number",
|
||||||
|
"calls",
|
||||||
|
"others",
|
||||||
|
"array",
|
||||||
|
"object"
|
||||||
|
}
|
||||||
|
-- Modules that have been loaded
|
||||||
|
local loadedModules = {}
|
||||||
|
|
||||||
|
local json_encode = {}
|
||||||
|
|
||||||
|
-- Configuration bases for client apps
|
||||||
|
local modes_defined = { "default", "strict" }
|
||||||
|
|
||||||
|
json_encode.default = {}
|
||||||
|
json_encode.strict = {
|
||||||
|
initialObject = true -- Require an object at the root
|
||||||
|
}
|
||||||
|
|
||||||
|
-- For each module, load it and its defaults
|
||||||
|
for _,name in ipairs(modulesToLoad) do
|
||||||
|
local mod = require("json.encode." .. name)
|
||||||
|
if mod.mergeOptions then
|
||||||
|
for _, mode in pairs(modes_defined) do
|
||||||
|
mod.mergeOptions(json_encode[mode], mode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
loadedModules[name] = mod
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Nested not found, so assume unsupported until use case arises
|
||||||
|
local function flattenOutput(out, value)
|
||||||
|
assert(type(value) ~= 'table')
|
||||||
|
out = out or {}
|
||||||
|
out[#out + 1] = value
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Prepares the encoding map from the already provided modules and new config
|
||||||
|
local function prepareEncodeMap(options)
|
||||||
|
local map = {}
|
||||||
|
for _, name in ipairs(modulesToLoad) do
|
||||||
|
local encodermap = loadedModules[name].getEncoder(options[name])
|
||||||
|
for valueType, encoderSet in pairs(encodermap) do
|
||||||
|
map[valueType] = flattenOutput(map[valueType], encoderSet)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return map
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Encode a value with a given encoding map and state
|
||||||
|
]]
|
||||||
|
local function encodeWithMap(value, map, state, isObjectKey)
|
||||||
|
local t = type(value)
|
||||||
|
local encoderList = assert(map[t], "Failed to encode value, unhandled type: " .. t)
|
||||||
|
for _, encoder in ipairs(encoderList) do
|
||||||
|
local ret = encoder(value, state, isObjectKey)
|
||||||
|
if false ~= ret then
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
end
|
||||||
|
error("Failed to encode value, encoders for " .. t .. " deny encoding")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function getBaseEncoder(options)
|
||||||
|
local encoderMap = prepareEncodeMap(options)
|
||||||
|
if options.preProcess then
|
||||||
|
local preProcess = options.preProcess
|
||||||
|
return function(value, state, isObjectKey)
|
||||||
|
local ret = preProcess(value, isObjectKey or false)
|
||||||
|
if nil ~= ret then
|
||||||
|
value = ret
|
||||||
|
end
|
||||||
|
return encodeWithMap(value, encoderMap, state)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return function(value, state, isObjectKey)
|
||||||
|
return encodeWithMap(value, encoderMap, state)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--[[
|
||||||
|
Retreive an initial encoder instance based on provided options
|
||||||
|
the initial encoder is responsible for initializing state
|
||||||
|
State has at least these values configured: encode, check_unique, already_encoded
|
||||||
|
]]
|
||||||
|
function json_encode.getEncoder(options)
|
||||||
|
options = options and util_merge({}, json_encode.default, options) or json_encode.default
|
||||||
|
local encode = getBaseEncoder(options)
|
||||||
|
|
||||||
|
local function initialEncode(value)
|
||||||
|
if options.initialObject then
|
||||||
|
local errorMessage = "Invalid arguments: expects a JSON Object or Array at the root"
|
||||||
|
assert(type(value) == 'table' and not isCall(value, options), errorMessage)
|
||||||
|
end
|
||||||
|
|
||||||
|
local alreadyEncoded = {}
|
||||||
|
local function check_unique(value)
|
||||||
|
assert(not alreadyEncoded[value], "Recursive encoding of value")
|
||||||
|
alreadyEncoded[value] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local outputEncoder = options.output and options.output() or output.getDefault()
|
||||||
|
local state = {
|
||||||
|
encode = encode,
|
||||||
|
check_unique = check_unique,
|
||||||
|
already_encoded = alreadyEncoded, -- To unmark encoding when moving up stack
|
||||||
|
outputEncoder = outputEncoder
|
||||||
|
}
|
||||||
|
local ret = encode(value, state)
|
||||||
|
if nil ~= ret then
|
||||||
|
return outputEncoder.simple and outputEncoder.simple(ret) or ret
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return initialEncode
|
||||||
|
end
|
||||||
|
|
||||||
|
-- CONSTRUCT STATE WITH FOLLOWING (at least)
|
||||||
|
--[[
|
||||||
|
encoder
|
||||||
|
check_unique -- used by inner encoders to make sure value is unique
|
||||||
|
already_encoded -- used to unmark a value as unique
|
||||||
|
]]
|
||||||
|
function json_encode.encode(data, options)
|
||||||
|
return json_encode.getEncoder(options)(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
local mt = {}
|
||||||
|
mt.__call = function(self, ...)
|
||||||
|
return json_encode.encode(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(json_encode, mt)
|
||||||
|
|
||||||
|
return json_encode
|
110
tools/luajson/json/encode/array.lua
Normal file
110
tools/luajson/json/encode/array.lua
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
|
||||||
|
local type = type
|
||||||
|
local pairs = pairs
|
||||||
|
local assert = assert
|
||||||
|
|
||||||
|
local table = require("table")
|
||||||
|
local math = require("math")
|
||||||
|
local table_concat = table.concat
|
||||||
|
local math_floor, math_modf = math.floor, math.modf
|
||||||
|
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local util_IsArray = jsonutil.IsArray
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
isArray = util_IsArray
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {}
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'array', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Utility function to determine whether a table is an array or not.
|
||||||
|
Criteria for it being an array:
|
||||||
|
* ExternalIsArray returns true (or false directly reports not-array)
|
||||||
|
* If the table has an 'n' value that is an integer >= 1 then it
|
||||||
|
is an array... may result in false positives (should check some values
|
||||||
|
before it)
|
||||||
|
* It is a contiguous list of values with zero string-based keys
|
||||||
|
]]
|
||||||
|
local function isArray(val, options)
|
||||||
|
local externalIsArray = options and options.isArray
|
||||||
|
|
||||||
|
if externalIsArray then
|
||||||
|
local ret = externalIsArray(val)
|
||||||
|
if ret == true or ret == false then
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Use the 'n' element if it's a number
|
||||||
|
if type(val.n) == 'number' and math_floor(val.n) == val.n and val.n >= 1 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local len = #val
|
||||||
|
for k,v in pairs(val) do
|
||||||
|
if type(k) ~= 'number' then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local _, decim = math_modf(k)
|
||||||
|
if not (decim == 0 and 1<=k) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if k > len then -- Use Lua's length as absolute determiner
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Cleanup function to unmark a value as in the encoding process and return
|
||||||
|
trailing results
|
||||||
|
]]
|
||||||
|
local function unmarkAfterEncode(tab, state, ...)
|
||||||
|
state.already_encoded[tab] = nil
|
||||||
|
return ...
|
||||||
|
end
|
||||||
|
local function getEncoder(options)
|
||||||
|
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
|
||||||
|
local function encodeArray(tab, state)
|
||||||
|
if not isArray(tab, options) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- Make sure this value hasn't been encoded yet
|
||||||
|
state.check_unique(tab)
|
||||||
|
local encode = state.encode
|
||||||
|
local compositeEncoder = state.outputEncoder.composite
|
||||||
|
local valueEncoder = [[
|
||||||
|
for i = 1, (composite.n or #composite) do
|
||||||
|
local val = composite[i]
|
||||||
|
PUTINNER(i ~= 1)
|
||||||
|
val = encode(val, state)
|
||||||
|
val = val or ''
|
||||||
|
if val then
|
||||||
|
PUTVALUE(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '[', ']', ',', tab, encode, state))
|
||||||
|
end
|
||||||
|
return { table = encodeArray }
|
||||||
|
end
|
||||||
|
|
||||||
|
local array = {
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
isArray = isArray,
|
||||||
|
getEncoder = getEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
return array
|
68
tools/luajson/json/encode/calls.lua
Normal file
68
tools/luajson/json/encode/calls.lua
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local table = require("table")
|
||||||
|
local table_concat = table.concat
|
||||||
|
|
||||||
|
local select = select
|
||||||
|
local getmetatable, setmetatable = getmetatable, setmetatable
|
||||||
|
local assert = assert
|
||||||
|
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
|
||||||
|
local isCall, decodeCall = jsonutil.isCall, jsonutil.decodeCall
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
}
|
||||||
|
|
||||||
|
-- No real default-option handling needed...
|
||||||
|
local modeOptions = {}
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'calls', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Encodes 'value' as a function call
|
||||||
|
Must have parameters in the 'callData' field of the metatable
|
||||||
|
name == name of the function call
|
||||||
|
parameters == array of parameters to encode
|
||||||
|
]]
|
||||||
|
local function getEncoder(options)
|
||||||
|
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
|
||||||
|
local function encodeCall(value, state)
|
||||||
|
if not isCall(value) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local encode = state.encode
|
||||||
|
local name, params = decodeCall(value)
|
||||||
|
local compositeEncoder = state.outputEncoder.composite
|
||||||
|
local valueEncoder = [[
|
||||||
|
for i = 1, (composite.n or #composite) do
|
||||||
|
local val = composite[i]
|
||||||
|
PUTINNER(i ~= 1)
|
||||||
|
val = encode(val, state)
|
||||||
|
val = val or ''
|
||||||
|
if val then
|
||||||
|
PUTVALUE(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
return compositeEncoder(valueEncoder, name .. '(', ')', ',', params, encode, state)
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
table = encodeCall,
|
||||||
|
['function'] = encodeCall
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local calls = {
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
getEncoder = getEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
return calls
|
58
tools/luajson/json/encode/number.lua
Normal file
58
tools/luajson/json/encode/number.lua
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local tostring = tostring
|
||||||
|
local assert = assert
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local huge = require("math").huge
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
nan = true,
|
||||||
|
inf = true
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {}
|
||||||
|
modeOptions.strict = {
|
||||||
|
nan = false,
|
||||||
|
inf = false
|
||||||
|
}
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function encodeNumber(number, options)
|
||||||
|
if number ~= number then
|
||||||
|
assert(options.nan, "Invalid number: NaN not enabled")
|
||||||
|
return "NaN"
|
||||||
|
end
|
||||||
|
if number == huge then
|
||||||
|
assert(options.inf, "Invalid number: Infinity not enabled")
|
||||||
|
return "Infinity"
|
||||||
|
end
|
||||||
|
if number == -huge then
|
||||||
|
assert(options.inf, "Invalid number: Infinity not enabled")
|
||||||
|
return "-Infinity"
|
||||||
|
end
|
||||||
|
return tostring(number)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getEncoder(options)
|
||||||
|
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
|
||||||
|
return {
|
||||||
|
number = function(number, state)
|
||||||
|
return encodeNumber(number, options)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local number = {
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
getEncoder = getEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
return number
|
77
tools/luajson/json/encode/object.lua
Normal file
77
tools/luajson/json/encode/object.lua
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local pairs = pairs
|
||||||
|
local assert = assert
|
||||||
|
|
||||||
|
local type = type
|
||||||
|
local tostring = tostring
|
||||||
|
|
||||||
|
local table_concat = require("table").concat
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {}
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'object', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Cleanup function to unmark a value as in the encoding process and return
|
||||||
|
trailing results
|
||||||
|
]]
|
||||||
|
local function unmarkAfterEncode(tab, state, ...)
|
||||||
|
state.already_encoded[tab] = nil
|
||||||
|
return ...
|
||||||
|
end
|
||||||
|
--[[
|
||||||
|
Encode a table as a JSON Object ( keys = strings, values = anything else )
|
||||||
|
]]
|
||||||
|
local function encodeTable(tab, options, state)
|
||||||
|
-- Make sure this value hasn't been encoded yet
|
||||||
|
state.check_unique(tab)
|
||||||
|
local encode = state.encode
|
||||||
|
local compositeEncoder = state.outputEncoder.composite
|
||||||
|
local valueEncoder = [[
|
||||||
|
local first = true
|
||||||
|
for k, v in pairs(composite) do
|
||||||
|
local ti = type(k)
|
||||||
|
assert(ti == 'string' or ti == 'number' or ti == 'boolean', "Invalid object index type: " .. ti)
|
||||||
|
local name = encode(tostring(k), state, true)
|
||||||
|
if first then
|
||||||
|
first = false
|
||||||
|
else
|
||||||
|
name = ',' .. name
|
||||||
|
end
|
||||||
|
PUTVALUE(name .. ':')
|
||||||
|
local val = encode(v, state)
|
||||||
|
val = val or ''
|
||||||
|
if val then
|
||||||
|
PUTVALUE(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '{', '}', nil, tab, encode, state))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getEncoder(options)
|
||||||
|
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
|
||||||
|
return {
|
||||||
|
table = function(tab, state)
|
||||||
|
return encodeTable(tab, options, state)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local object = {
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
getEncoder = getEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
return object
|
66
tools/luajson/json/encode/others.lua
Normal file
66
tools/luajson/json/encode/others.lua
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local tostring = tostring
|
||||||
|
|
||||||
|
local assert = assert
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local type = type
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
-- Shortcut that works
|
||||||
|
local encodeBoolean = tostring
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
allowUndefined = true,
|
||||||
|
null = jsonutil.null,
|
||||||
|
undefined = jsonutil.undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {}
|
||||||
|
|
||||||
|
modeOptions.strict = {
|
||||||
|
allowUndefined = false
|
||||||
|
}
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
local function getEncoder(options)
|
||||||
|
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
|
||||||
|
local function encodeOthers(value, state)
|
||||||
|
if value == options.null then
|
||||||
|
return 'null'
|
||||||
|
elseif value == options.undefined then
|
||||||
|
assert(options.allowUndefined, "Invalid value: Unsupported 'Undefined' parameter")
|
||||||
|
return 'undefined'
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function encodeBoolean(value, state)
|
||||||
|
return value and 'true' or 'false'
|
||||||
|
end
|
||||||
|
local nullType = type(options.null)
|
||||||
|
local undefinedType = options.undefined and type(options.undefined)
|
||||||
|
-- Make sure that all of the types handled here are handled
|
||||||
|
local ret = {
|
||||||
|
boolean = encodeBoolean,
|
||||||
|
['nil'] = function() return 'null' end,
|
||||||
|
[nullType] = encodeOthers
|
||||||
|
}
|
||||||
|
if undefinedType then
|
||||||
|
ret[undefinedType] = encodeOthers
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
local others = {
|
||||||
|
encodeBoolean = encodeBoolean,
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
getEncoder = getEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
return others
|
91
tools/luajson/json/encode/output.lua
Normal file
91
tools/luajson/json/encode/output.lua
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local type = type
|
||||||
|
local assert, error = assert, error
|
||||||
|
local table_concat = require("table").concat
|
||||||
|
local loadstring = loadstring or load
|
||||||
|
|
||||||
|
local io = require("io")
|
||||||
|
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
|
||||||
|
local output_utility = require("json.encode.output_utility")
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local tableCompositeCache = setmetatable({}, {__mode = 'v'})
|
||||||
|
|
||||||
|
local TABLE_VALUE_WRITER = [[
|
||||||
|
ret[#ret + 1] = %VALUE%
|
||||||
|
]]
|
||||||
|
|
||||||
|
local TABLE_INNER_WRITER = ""
|
||||||
|
|
||||||
|
--[[
|
||||||
|
nextValues can output a max of two values to throw into the data stream
|
||||||
|
expected to be called until nil is first return value
|
||||||
|
value separator should either be attached to v1 or in innerValue
|
||||||
|
]]
|
||||||
|
local function defaultTableCompositeWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state)
|
||||||
|
if type(nextValues) == 'string' then
|
||||||
|
local fun = output_utility.prepareEncoder(defaultTableCompositeWriter, nextValues, innerValue, TABLE_VALUE_WRITER, TABLE_INNER_WRITER)
|
||||||
|
local ret = {}
|
||||||
|
fun(composite, ret, encode, state)
|
||||||
|
return beginValue .. table_concat(ret, innerValue) .. closeValue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- no 'simple' as default action is just to return the value
|
||||||
|
local function getDefault()
|
||||||
|
return { composite = defaultTableCompositeWriter }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- BEGIN IO-WRITER OUTPUT
|
||||||
|
local IO_INNER_WRITER = [[
|
||||||
|
if %WRITE_INNER% then
|
||||||
|
state.__outputFile:write(%INNER_VALUE%)
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
local IO_VALUE_WRITER = [[
|
||||||
|
state.__outputFile:write(%VALUE%)
|
||||||
|
]]
|
||||||
|
|
||||||
|
local function buildIoWriter(output)
|
||||||
|
if not output then -- Default to stdout
|
||||||
|
output = io.output()
|
||||||
|
end
|
||||||
|
local function ioWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state)
|
||||||
|
-- HOOK OUTPUT STATE
|
||||||
|
state.__outputFile = output
|
||||||
|
if type(nextValues) == 'string' then
|
||||||
|
local fun = output_utility.prepareEncoder(ioWriter, nextValues, innerValue, IO_VALUE_WRITER, IO_INNER_WRITER)
|
||||||
|
local ret = {}
|
||||||
|
output:write(beginValue)
|
||||||
|
fun(composite, ret, encode, state)
|
||||||
|
output:write(closeValue)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ioSimpleWriter(encoded)
|
||||||
|
if encoded then
|
||||||
|
output:write(encoded)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return { composite = ioWriter, simple = ioSimpleWriter }
|
||||||
|
end
|
||||||
|
local function getIoWriter(output)
|
||||||
|
return function()
|
||||||
|
return buildIoWriter(output)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local output = {
|
||||||
|
getDefault = getDefault,
|
||||||
|
getIoWriter = getIoWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
54
tools/luajson/json/encode/output_utility.lua
Normal file
54
tools/luajson/json/encode/output_utility.lua
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local assert, loadstring = assert, loadstring or load
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
-- Key == weak, if main key goes away, then cache cleared
|
||||||
|
local outputCache = setmetatable({}, {__mode = 'k'})
|
||||||
|
-- TODO: inner tables weak?
|
||||||
|
|
||||||
|
local function buildFunction(nextValues, innerValue, valueWriter, innerWriter)
|
||||||
|
local putInner = ""
|
||||||
|
if innerValue and innerWriter then
|
||||||
|
-- Prepare the lua-string representation of the separator to put in between values
|
||||||
|
local formattedInnerValue = ("%q"):format(innerValue)
|
||||||
|
-- Fill in the condition %WRITE_INNER% and the %INNER_VALUE% to actually write
|
||||||
|
putInner = innerWriter:gsub("%%WRITE_INNER%%", "%%1"):gsub("%%INNER_VALUE%%", formattedInnerValue)
|
||||||
|
end
|
||||||
|
-- Template-in the value writer (if present) and its conditional argument
|
||||||
|
local functionCode = nextValues:gsub("PUTINNER(%b())", putInner)
|
||||||
|
-- %VALUE% is to be filled in by the value-to-write
|
||||||
|
valueWriter = valueWriter:gsub("%%VALUE%%", "%%1")
|
||||||
|
-- Template-in the value writer with its argument
|
||||||
|
functionCode = functionCode:gsub("PUTVALUE(%b())", valueWriter)
|
||||||
|
functionCode = [[
|
||||||
|
return function(composite, ret, encode, state)
|
||||||
|
]] .. functionCode .. [[
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
return assert(loadstring(functionCode))()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function prepareEncoder(cacheKey, nextValues, innerValue, valueWriter, innerWriter)
|
||||||
|
local cache = outputCache[cacheKey]
|
||||||
|
if not cache then
|
||||||
|
cache = {}
|
||||||
|
outputCache[cacheKey] = cache
|
||||||
|
end
|
||||||
|
local fun = cache[nextValues]
|
||||||
|
if not fun then
|
||||||
|
fun = buildFunction(nextValues, innerValue, valueWriter, innerWriter)
|
||||||
|
cache[nextValues] = fun
|
||||||
|
end
|
||||||
|
return fun
|
||||||
|
end
|
||||||
|
|
||||||
|
local output_utility = {
|
||||||
|
prepareEncoder = prepareEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
return output_utility
|
88
tools/luajson/json/encode/strings.lua
Normal file
88
tools/luajson/json/encode/strings.lua
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local string_char = require("string").char
|
||||||
|
local pairs = pairs
|
||||||
|
|
||||||
|
local jsonutil = require("json.util")
|
||||||
|
local util_merge = jsonutil.merge
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local normalEncodingMap = {
|
||||||
|
['"'] = '\\"',
|
||||||
|
['\\'] = '\\\\',
|
||||||
|
['/'] = '\\/',
|
||||||
|
['\b'] = '\\b',
|
||||||
|
['\f'] = '\\f',
|
||||||
|
['\n'] = '\\n',
|
||||||
|
['\r'] = '\\r',
|
||||||
|
['\t'] = '\\t',
|
||||||
|
['\v'] = '\\v' -- not in official spec, on report, removing
|
||||||
|
}
|
||||||
|
|
||||||
|
local xEncodingMap = {}
|
||||||
|
for char, encoded in pairs(normalEncodingMap) do
|
||||||
|
xEncodingMap[char] = encoded
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Pre-encode the control characters to speed up encoding...
|
||||||
|
-- NOTE: UTF-8 may not work out right w/ JavaScript
|
||||||
|
-- JavaScript uses 2 bytes after a \u... yet UTF-8 is a
|
||||||
|
-- byte-stream encoding, not pairs of bytes (it does encode
|
||||||
|
-- some letters > 1 byte, but base case is 1)
|
||||||
|
for i = 0, 255 do
|
||||||
|
local c = string_char(i)
|
||||||
|
if c:match('[%z\1-\031\128-\255]') and not normalEncodingMap[c] then
|
||||||
|
-- WARN: UTF8 specializes values >= 0x80 as parts of sequences...
|
||||||
|
-- without \x encoding, do not allow encoding > 7F
|
||||||
|
normalEncodingMap[c] = ('\\u%.4X'):format(i)
|
||||||
|
xEncodingMap[c] = ('\\x%.2X'):format(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local defaultOptions = {
|
||||||
|
xEncode = false, -- Encode single-bytes as \xXX
|
||||||
|
processor = nil, -- Simple processor for the string prior to quoting
|
||||||
|
-- / is not required to be quoted but it helps with certain decoding
|
||||||
|
-- Required encoded characters, " \, and 00-1F (0 - 31)
|
||||||
|
encodeSet = '\\"/%z\1-\031',
|
||||||
|
encodeSetAppend = nil -- Chars to append to the default set
|
||||||
|
}
|
||||||
|
|
||||||
|
local modeOptions = {}
|
||||||
|
|
||||||
|
local function mergeOptions(options, mode)
|
||||||
|
jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getEncoder(options)
|
||||||
|
options = options and util_merge({}, defaultOptions, options) or defaultOptions
|
||||||
|
local encodeSet = options.encodeSet
|
||||||
|
if options.encodeSetAppend then
|
||||||
|
encodeSet = encodeSet .. options.encodeSetAppend
|
||||||
|
end
|
||||||
|
local encodingMap = options.xEncode and xEncodingMap or normalEncodingMap
|
||||||
|
local encodeString
|
||||||
|
if options.processor then
|
||||||
|
local processor = options.processor
|
||||||
|
encodeString = function(s, state)
|
||||||
|
return '"' .. processor(s:gsub('[' .. encodeSet .. ']', encodingMap)) .. '"'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
encodeString = function(s, state)
|
||||||
|
return '"' .. s:gsub('[' .. encodeSet .. ']', encodingMap) .. '"'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
string = encodeString
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local strings = {
|
||||||
|
mergeOptions = mergeOptions,
|
||||||
|
getEncoder = getEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings
|
152
tools/luajson/json/util.lua
Normal file
152
tools/luajson/json/util.lua
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
--[[
|
||||||
|
Licensed according to the included 'LICENSE' document
|
||||||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||||||
|
]]
|
||||||
|
local type = type
|
||||||
|
local print = print
|
||||||
|
local tostring = tostring
|
||||||
|
local pairs = pairs
|
||||||
|
local getmetatable, setmetatable = getmetatable, setmetatable
|
||||||
|
local select = select
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
local function foreach(tab, func)
|
||||||
|
for k, v in pairs(tab) do
|
||||||
|
func(k,v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function printValue(tab, name)
|
||||||
|
local parsed = {}
|
||||||
|
local function doPrint(key, value, space)
|
||||||
|
space = space or ''
|
||||||
|
if type(value) == 'table' then
|
||||||
|
if parsed[value] then
|
||||||
|
print(space .. key .. '= <' .. parsed[value] .. '>')
|
||||||
|
else
|
||||||
|
parsed[value] = key
|
||||||
|
print(space .. key .. '= {')
|
||||||
|
space = space .. ' '
|
||||||
|
foreach(value, function(key, value) doPrint(key, value, space) end)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if type(value) == 'string' then
|
||||||
|
value = '[[' .. tostring(value) .. ']]'
|
||||||
|
end
|
||||||
|
print(space .. key .. '=' .. tostring(value))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
doPrint(name, tab)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function clone(t)
|
||||||
|
local ret = {}
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
ret[k] = v
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
local function inner_merge(t, remaining, from, ...)
|
||||||
|
if remaining == 0 then
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
if from then
|
||||||
|
for k,v in pairs(from) do
|
||||||
|
t[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return inner_merge(t, remaining - 1, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[*
|
||||||
|
Shallow-merges tables in order onto the first table.
|
||||||
|
|
||||||
|
@param t table to merge entries onto
|
||||||
|
@param ... sequence of 0 or more tables to merge onto 't'
|
||||||
|
|
||||||
|
@returns table 't' from input
|
||||||
|
]]
|
||||||
|
local function merge(t, ...)
|
||||||
|
return inner_merge(t, select('#', ...), ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Function to insert nulls into the JSON stream
|
||||||
|
local function null()
|
||||||
|
return null
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Marker for 'undefined' values
|
||||||
|
local function undefined()
|
||||||
|
return undefined
|
||||||
|
end
|
||||||
|
|
||||||
|
local ArrayMT = {}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Return's true if the metatable marks it as an array..
|
||||||
|
Or false if it has no array component at all
|
||||||
|
Otherwise nil to get the normal detection component working
|
||||||
|
]]
|
||||||
|
local function IsArray(value)
|
||||||
|
if type(value) ~= 'table' then return false end
|
||||||
|
local meta = getmetatable(value)
|
||||||
|
local ret = meta == ArrayMT or (meta ~= nil and meta.__is_luajson_array)
|
||||||
|
if not ret then
|
||||||
|
if #value == 0 then return false end
|
||||||
|
else
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function InitArray(array)
|
||||||
|
setmetatable(array, ArrayMT)
|
||||||
|
return array
|
||||||
|
end
|
||||||
|
|
||||||
|
local CallMT = {}
|
||||||
|
|
||||||
|
local function isCall(value)
|
||||||
|
return CallMT == getmetatable(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function buildCall(name, ...)
|
||||||
|
local callData = {
|
||||||
|
name = name,
|
||||||
|
parameters = {n = select('#', ...), ...}
|
||||||
|
}
|
||||||
|
return setmetatable(callData, CallMT)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function decodeCall(callData)
|
||||||
|
if not isCall(callData) then return nil end
|
||||||
|
return callData.name, callData.parameters
|
||||||
|
end
|
||||||
|
|
||||||
|
local function doOptionMerge(options, nested, name, defaultOptions, modeOptions)
|
||||||
|
if nested then
|
||||||
|
modeOptions = modeOptions and modeOptions[name]
|
||||||
|
defaultOptions = defaultOptions and defaultOptions[name]
|
||||||
|
end
|
||||||
|
options[name] = merge(
|
||||||
|
{},
|
||||||
|
defaultOptions,
|
||||||
|
modeOptions,
|
||||||
|
options[name]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local json_util = {
|
||||||
|
printValue = printValue,
|
||||||
|
clone = clone,
|
||||||
|
merge = merge,
|
||||||
|
null = null,
|
||||||
|
undefined = undefined,
|
||||||
|
IsArray = IsArray,
|
||||||
|
InitArray = InitArray,
|
||||||
|
isCall = isCall,
|
||||||
|
buildCall = buildCall,
|
||||||
|
decodeCall = decodeCall,
|
||||||
|
doOptionMerge = doOptionMerge
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_util
|
95
tools/luajson/test_json.lua
Normal file
95
tools/luajson/test_json.lua
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package.path = package.path .. ";lua_scripts/libraries/luajson/?.lua"
|
||||||
|
|
||||||
|
local JSON = require"json"
|
||||||
|
|
||||||
|
local jsontest = [[{ 1:{"scn_ptz_id":"",
|
||||||
|
"scn_ptz_prepos":"Preset 176",
|
||||||
|
"scn_ptz_order":1,
|
||||||
|
"scn_ptz_duration":"30",
|
||||||
|
"scn_ptz_rally_delay":"2"}
|
||||||
|
,
|
||||||
|
2:{"scn_ptz_id":"","scn_ptz_prepos":"route","scn_ptz_order":2,"scn_ptz_duration":"30","scn_ptz_rally_delay":"2"} }
|
||||||
|
]]
|
||||||
|
local jsontest2 = [[{
|
||||||
|
"extension":"mpg",
|
||||||
|
"id":1545148451781,
|
||||||
|
"name":"Foule_1280x720p.mpg",
|
||||||
|
"size":67240746,
|
||||||
|
"date":1545148451,
|
||||||
|
"mime":"video\/mpeg",
|
||||||
|
"filename":"1545148451781.mpg",
|
||||||
|
"dir":"\/home\/pixalarm_data\/fileprocessor_data",
|
||||||
|
"function_metadatas":
|
||||||
|
{
|
||||||
|
"function_faceblur":
|
||||||
|
{
|
||||||
|
"date":1545228627,
|
||||||
|
"current_frame":"845",
|
||||||
|
"polygons":[
|
||||||
|
{
|
||||||
|
"polygon_id":"new_1",
|
||||||
|
"polygon_vertex":"[
|
||||||
|
[0.14254859611231102,0.12476007677543186],[0.13174946004319654,0.4740882917466411],
|
||||||
|
[0.3898488120950324,0.6621880998080614],[0.4038876889848812,0.11516314779270634]
|
||||||
|
]",
|
||||||
|
"polygon_frame_start":"1",
|
||||||
|
"polygon_frame_stop":"300",
|
||||||
|
"polygon_type":"full_blur"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"polygon_id":"new_2",
|
||||||
|
"polygon_vertex":"[
|
||||||
|
[0.6198704103671706,0.1727447216890595],[0.5496760259179265,0.6007677543186181],
|
||||||
|
[0.7775377969762419,0.7946257197696737],[0.9028077753779697,0.761996161228407],
|
||||||
|
[0.9481641468682506,0.2821497120921305],[0.7829373650107991,0.04798464491362764]
|
||||||
|
]",
|
||||||
|
"polygon_frame_start":"200",
|
||||||
|
"polygon_frame_stop":"845",
|
||||||
|
"polygon_type":"no_blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"framecuts":[
|
||||||
|
["17","110"],
|
||||||
|
["248","298"],
|
||||||
|
["488","620"],
|
||||||
|
["378","428"]
|
||||||
|
],
|
||||||
|
"face_selection":[
|
||||||
|
{
|
||||||
|
"frame":"21",
|
||||||
|
"x":"0.5",
|
||||||
|
"y":"0.356"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame":"108",
|
||||||
|
"x":"0.4289",
|
||||||
|
"y":"0.275"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame":"294",
|
||||||
|
"x":"0.726",
|
||||||
|
"y":"0.2364"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blur_type":"blur",
|
||||||
|
"blur_area":"face"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total_frame":"845",
|
||||||
|
"status":"DECODE_FINISHED",
|
||||||
|
"fps":"25.00"
|
||||||
|
}]]
|
||||||
|
|
||||||
|
local res = JSON.decode(jsontest2)
|
||||||
|
for k, v in pairs(res) do
|
||||||
|
print( k, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
res = JSON.decode( '{"content" : {},"date" : "2014-12-30T08:29:48Z","error" : {"code" : 0,"httpcode" : 200,"message" : ""},"status" : 1}' )
|
||||||
|
for k, v in pairs(res) do
|
||||||
|
print( k, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
local jsondata = JSON.encode( res )
|
||||||
|
print(jsondata)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user