Update talents + beasts

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

View File

@ -37,13 +37,13 @@ Pour le compendium :
**!!! 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
-> Bless -> Jerôme Paire
**OK** -> Bookish:Contorsionist -> Dr.Droide
-> Coolheaded:Embezzle -> Moilu
-> Enclosed Fighter:Gregarious -> Gharazel
**OK** -> Coolheaded:Embezzle -> Moilu
**OK** -> Enclosed Fighter:Gregarious -> Gharazel
**OK** -> Gunner:Kingpin -> Dr.Droide
* Divers ajustements de termes ici et là - Debug en cours

File diff suppressed because one or more lines are too long

View File

@ -159,152 +159,152 @@
{
"id": "Coolheaded",
"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",
"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",
"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",
"name": "Criminel",
"description": "<p>You are an active criminal making money from illegal sources, and youre 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>&nbsp;</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&nbsp;</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>&nbsp;</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, its 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>&nbsp;</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&nbsp;</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>&nbsp;</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 dachat.</p>"
},
{
"id": "Deadeye Shot",
"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",
"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>&nbsp;</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>&nbsp;</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",
"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",
"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",
"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>&nbsp;</p>\n<p>Remarque: utiliser ce Talent sera considéré comme une tricherie dans tout combat officiel.</p>"
},
{
"id": "Disarm",
"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 opponents 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",
"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",
"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",
"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 youve 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",
"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",
"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",
"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",
"name": "Etiquette",
"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 GMs discretion.</p>"
"name": "Savoir-vivre",
"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",
"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",
"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",
"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",
"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",
"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",
"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",
"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!",
"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",
"name": "Véloce",
"description": "You gain +1 to your Movement Attribute."
"description": "Vous gagnez +1 à votre Attribut de Mouvement."
},
{
"id": "Frenzy",
"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;\">&nbsp;</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&nbsp;<em style=\"user-select: text;\">Stunned</em>&nbsp;or&nbsp;<em style=\"user-select: text;\">Unconscious</em>&nbsp;condition. After your Frenzy is over you immediately receive a&nbsp;<em style=\"user-select: text;\">Fatigued</em>&nbsp;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;\">&nbsp;</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 &nbsp;<em style=\"user-select: text;\">Stunned</em>&nbsp;or&nbsp;<em style=\"user-select: text;\">Inconscient</em>&nbsp;. Dès que votre Frénésie s'achève, vous gagnez l'État&nbsp;<em style=\"user-select: text;\">Exténué</em>&nbsp;.</p>"
},
{
"id": "Frightening",
"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",
"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",
"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",

1
tools/beasts.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,263 @@
{
"label": "Bestiaire",
"entries": [
{
"id": "Basilisk",
"name": "Basilic",
"description": "<p>Ces créatures reptiliennes à huit pattes sont solitaires et insaisissables. Considérées comme étant l'une des plus anciennes créatures du Chaos, on les dit emplies de tant de malveillance et de venin que même le sol sur lequel elles marchent est empoisonné. Leur morsure est également venimeuse, mais c'est leur regard pétrifiant qui est le plus à craindre. <br> Rarement rencontrés de nos jours, les basilics sont des ennemis des plus dangereux. Leurs glandes et leurs organes sont très prisés des Magiciens et Alchimistes. Ce qui fait que des chasseurs opportunistes écoutent souvent les rumeurs sur la présence de basilics dans les espaces sauvages du Vorbergland, et bien peu en reviennent.</p>"
},
{
"id": "Bear",
"name": "Ours",
"description": "<p>Arpentant les étendues sauvages du Reikland, les ours sont solitaires et généralement farouches, ne devenant agressifs que lorsque des intrus menacent leurs petits ou s'ils sont blessés. Quand la nourriture se raréfie, ils se rapprochent des villages non protégés et des voyageurs, en particulier ceux qui se montrent négligents avec les provisions.</p>"
},
{
"id": "Bloodletter of Khorne",
"name": "Sanguinaire de Khorne",
"description": "<p>Élus de Khorne, les sanguinaires règnent sur les champs de bataille du Vieux Monde, arrachant crânes et vies en l'honneur du Dieu Sang. Des dents pointues et acérées dépassent de leurs monstrueux visages cornus. Leur peau rouge-sang est dure comme l'airain, forgée sur l'enclume de guerres incessantes. Chaque sanguinaire porte une lame d'Enfer, diaboliquement tranchante et trempée dans le sang versé, qu'il manient avec témérité, se délectant du doux plaisir de la tuerie aveugle. </p>"
},
{
"id": "Boar",
"name": "Sanglier",
"description": "<p>A la recherche de nourriture dans la forêt, les sangliers sont des créatures solitaires, qui lorsqu'ils sont acculés, se battent en utilisant leurs défenses aiguisées avec une ténacité qui stupéfait ceux qui n'y sont pas préparés. Bien que la plupart mesurent entre 1m50 et 1m80 de long, certains atteignent des proportions remarquables. Les plus grands d'entre eux sont très prisés par les Orcs, qui les utilisent comme de féroces montures de guerre.</p>"
},
{
"id": "Bog Octopus",
"name": "Pieuvre des Tourbières",
"description": "<p>Les pieuvres des tourbières se cachent dans les eaux peu profondes, généralement dans les marécages. Elles y attendent leurs proies dans une immobilité parfaite, capables de sentir les vibrations des créatures en approche, puis surgissant de l'eau boueuse, cherchant à agripper et à noyer en utilisant leurs robustes tentacules. Généralement marbrées de vert et de brun, les pieuvres des tourbières bénéficient d'un camouflage parfaitement adapté aux environnements marécageux, leurs immenses yeux limpides étant souvent le seul indice trahissant leur présence. Beaucoup possèdent des tentacules de six mètres et plus et un corps de plus d'un mètre quatre-vingts de long, mais des histoires prétendent qu'elles atteignent plusieurs fois cette taille, en particulier si elles ont un apport régulier supplémentaire en viande.</p>"
},
{
"id": "Bray-Shaman",
"name": "Chaman-Bray",
"description": "<p>Les Chaman-Brays sont nés avec la capacité de manier les pouvoirs du Chaos, qu'ils utilisent avec un talent terrifiant. Ils n'ont jamais besoin de se défendre eux-même contre les autres membres de leur horde, ce qui est exceptionnel chez les hommes-bêtes. Personne n'oserait les attaquer puisqu'ils représentent la volonté des Sombres dieux en personne.</p>"
},
{
"id": "Cairn Wraith",
"name": "Spectre de Cairn",
"description": "<p>Les Spectres de Cairn sont des esprits particulièrement puissants, les restes spectraux d'aspirants nécromanciens qui ont cherché à prolonger leur existence grâce à la magie sombre. De leur vivant, ils étaient obstinés ; dans la mort, leur volonté malveillante les pousse à exercer une terrible vengeance sur les âmes ardentes des vivants. Beaucoup de ces Spectres hantent les cairns couverts de brume qui parsèment le paysage de l'Empire, tels que les Hägercrybs déchues.</p>"
},
{
"id": "Cave Squig",
"name": "Squig des Cavernes",
"description": "<p><Les squigs sont de grandes créatures fongoïdes, en général de forme rondes, qui vivent sous terre dans les grottes les plus profondes et les plus sombres. Ils arborent une gueule béante et de grandes dents pointues. Ils sont prisés des gobelins pour leur chair et leur peau, mais aussi comme gardes ou animaux de compagnie./p>"
},
{
"id": "Chaos Warrior",
"name": "Guerrier du Chaos",
"description": "<p>Énormes brutes lourdement cuirassées, affublées de pointes et dimpressionnants symboles de leurs dieux, les Guerriers du Chaos ne sont clairement plus des humains. Plus rien ne subsiste de leur existence passée, et ils n'existent que pour servir leur sombre chef et rien d'autre. Alors que la plupart des Guerriers du Chaos sont des maraudeurs de haut rang venant des régions loin au nord, quelques cultistes privilégiés peuvent se voir attribuer une armure du Chaos par les Sombres dieux, leur conférant une grande puissance, avec obligation de ne jamais l'ôter de leur vie. Comme peu de guerriers surpassent et qu'aucun chevalier n'est mieux protégé, leur espérance de vie demeure très longue.</p>"
},
{
"id": "Clanrat",
"name": "Guerrier des Clans",
"description": "<p>La plupart des Skavens sont originaires de l'un des nombreux et obscurs clans qui se chamaillent constamment pour des raisons diplomatiques, se poignardent dans le dos, ou encore déclarent la guerre aux autres. Ils agissent généralement sur les ordres de Skavens de statut supérieur, mais cherchent toujours un moyen d'obtenir une meilleure position, le plus souvent par trahison. Ils revêtent habituellement du cuir moisi ou des vêtements sales, avec des bouts de métal rouillé et terni leur servant d'armure. Les Guerriers des Clans sont souvent envoyés comme éclaireurs ou pillards pour récupérer des denrées, chercher des malepierres ou rafler des esclaves.</p>"
},
{
"id": "Crypt Ghoul",
"name": "Goule de Crypte",
"description": "<p>Parmi les créatures les plus pitoyables du Vieux Monde, les goules de cryptes sont des créatures voûtées et hideuses, à la peau cireuse et sale, et dont les dents jaunes bien aiguisées sont capables de déchirer la chair de leurs victimes. Les goules sont attirées par les énergies magiques de Shyish et de Dhar, ce qui signifie dans les faits qu'elles tournent autour vers des cimetières, des cryptes et des champs de bataille.</p>"
},
{
"id": "Cultist",
"name": "Cultiste",
"description": "<p>Pour ceux qui ne sont pas atteints par le fléau du Chaos, il ne représente qu'une abomination contre nature, mais pour ceux qu'il affecte, chaque pas vers la damnation, chaque idée les rapprochant du Chaos semble non seulement logique mais indispensable. Certains adeptes particulièrement dévoués des cultes proscrits reçoivent des \"dons\" de leurs dieux : d'immondes mutations qui les condamneront à mort si les Répurgateurs découvrent leur secret.</p>"
},
{
"id": "Daemonette of Slaanesh",
"name": "Démonette of Slaanesh",
"description": "<p>Comme toutes les créatures du Prince de la Souffrance et du Plaisir, les Démonettes de Slaanesh sont aussi belles qu'horribles. Leur allure surnaturelle défiant tout sens et toute rationalité, elles rendent leurs ennemis incapables de résister, tant ils sont charmés par la sensualité de leurs formes monstrueuses. Elles arborent une peau crémeuse et pâle et de grands yeux noirs comme du jais, ainsi qu'une épaisse chevelure flottant au vent et de couleur contre nature. Au lieu de mains, leurs bras sveltes se terminent par des griffes recourbées aux allures de pinces de crabe.</p>"
},
{
"id": "Demigryph",
"name": "Demigriffon",
"description": "<p>Avec leur tête d'aigle et leur corps de lion, les demigriffons sont de puissantes créatures au port altier. Ils arpentent les forêts et les prairies de l'Empire, généralement loin des habitations humaines, chassant seuls. Des demigriffons captifs sont utilisés par les Ordres de chevaliers les plus indomptables de l'Empire comme destrier de guerre. Contrairement aux plus grandes montures monstrueuses, généralement capturées jeunes et élevées en captivité, la capture d'un demigriffon adulte est un rite de passage dans certains ordres.</p>"
},
{
"id": "Dire Wolf",
"name": "Loup funeste",
"description": "<p>Quand la terre restitue les morts, il n'y a pas que les humains qui se relèvent : parodies macabres de loups géants aux yeux rougeoyants, aux lambeaux de chair tombant de leurs carcasses putréfiées, les Loups funestes rôdent la nuit pour le compte de leurs maîtres nécromanciens. Dans le Reikland, on dit qu'ils parcourent les forêts hantées des Hägercrybs lorsque Morrlieb est pleine, à la recherche d'une proies pour assouvir leur insatiable faim, rongés par un appétit de chair et un instinct accru pour l'odeur du sang.</p>"
},
{
"id": "Dog",
"name": "Chien",
"description": "<p>Les chiens sont élevés pour une myriade de raisons à travers le Vieux Monde. Alors que les toutous choyés des courtisans d'Altdorf ne sont une menace que pour la dignité d'un Joueur, les races plus grandes, y compris celles élevées pour la guerre et les combats de chiens, peuvent représenter une menace considérable.</p>"
},
{
"id": "Dragon",
"name": "Dragon",
"description": "<p>Les dragons régnaient sur les cieux bien avant que les anciennes races n'arpentent le Monde Connu. Même si les Dragons d'aujourd'hui ne sont plus que l'ombre de leurs frères, ils demeurent parmi les créatures connues les plus puissantes de l'Empire. Les quelques anciens Dragons survivants sont immenses et sortent rarement de leur léthargie. S'ils prennent ombrage de la présence d'un petit groupe d'aventuriers, ces derniers ne pourront pas faire grand-chose pour lui résister.</p>"
},
{
"id": "Fenbeast",
"name": "Bête des marais",
"description": "<p>Animés par les lanceurs de Sorts à partir d'immondices des marais et des tourbières, les Bêtes des Marais ne sont que des automates parfaitement stupides créés par magie. Vaguement humanoïdes, ils sont constitués de boue, d'os, de branches et de mucus, et nécéssitent d'importantes quantités de magie pour conserver leur intégrité. Parfois, ils sont animés par des Magicien spécialisés dans la Magie de Jade pour servir de gardes du corps ou pour accomplir une tâche spécifique nécessitant de la force brute ou un meurtre aveugle. Ils s'animent parfois de façon autonome, engendrés dans des bassins d'eau stagnante où le flux de Magie a été corrompu. Le Collège de Jade d'Altdorf conserve une vingtaine de ces créatures comme bêtes de somme, qui effectuent es corvées pour les Magisters supérieurs, maintenu par la focalisation incessante de douzaines d'apprentis.</p>"
},
{
"id": "Fimir",
"name": "Fimir",
"description": "<p>Les fimirs sont des créatures reptiliennes discrètes, pourvues d'un seul oeil. Ils chassent dans les recoins sombres des marais et des tourbières de l'ouest du Reikland, mais fuient la lumière du soleil, n'apparaissant habituellement qu'à l'aube ou au crépuscule, ou sous le couvert de la brume et du brouillard pour traquer des victimes à des fins mystérieuses. Des sorcières humaines inconscientes ou suffisamment désespérées pour se mêler des affaires des Démons ont été appelées pour rechercher un Fimirs dans le but de mettre au jour ses pernicieux mystères, car on prétend qu'elles invoquent et contrôlent ces entités. Et quand à savoir s'il est plus risqué de traiter avec les Puissances de la Ruine ou avec les Fimir, à chacun de le décider. Les Fimirs forment une société matriarcale. Le chef de chaque clan est une puissante sorcière appelée Meargh, aidée par un groupe de lanceurs de Sorts inférieurs appelé Dirach. La majorité des membres du clan sont de humbles Sheals, qui sont protégés par une caste de guerriers, les Fimms, au queues garnis de pointes et de boules osseues, utilisés pour briser les membres des adversaires imprudents.</p>"
},
{
"id": "Fr'hough Mournbreath",
"name": "Fr'hough Mournbreath"
},
{
"id": "Ghost",
"name": "Fantôme",
"description": "<p>Les fantômes sont les esprits d'âmes tourmentées, mortes alors que leurs affaires terrestres n'étaient pas réglées. Tout comme les squelettes et les zombies, les fantômes peuvent être invoqués par des vampires ou des nécromanciens, ou peuvent hanter des régions imprégnées de Dhar. Dans des cas exceptionnels, des esprits particulièrement déterminés peuvent se frayer un chemin depuis le royaume de Morr pour poursuivre leurs propres affaires, bien que de tels événements attirent rapidement l'attention du culte de Morr, ou des Magisters de l'Ordre de'Améthystes. Lorsqu'ils sont invoqués grâce aux arts nécromantiques, les fantômes se regroupent, formant de grandes armées d'esprits qui fondent sur leurs ennemis, répandant la peur et le désarroi dans leur passage.</p>"
},
{
"id": "Giant",
"name": "Géant",
"description": "<p>Les géants sont des créatures solitaires qui évitent généralement la civilisation. La plupart chassent dans les endroits éloignés et montagneux entourant l'Empire, se cachent dans des grottes et des ruines oubliées, loin des peuples plus petits, bien qu'ils migrent parfois dans les contreforts à la recherche de nourriture. Ils ont la réputation d'être belliqueux et féroces, à cause de leur taille impressionnante, de leur régime alimentaire à base de bétail et de leur association avec des armées de Peaux Vertes en maraude qui les asservissent. En réalité, beaucoup de géants ont un bon fond, sont en proie à des crises de colère et défendent farouchement leur vie privé, mais ne sont pas nécessairement hostiles aux autres.</p>"
},
{
"id": "Giant Rat",
"name": "Rat géant",
"description": "<p>Les rats s'infiltrent partout et sont particulièrement répandus dans les cités et les villes. Plus la population est dense, plus les rats s'entassent avec eux, ce qui est particulièrement néfaste quand les créatures sont porteuses de maladies. Généralement particulièrement petits, ils peuvent atteindre des proportions monstrueuses, et l'on rapporte la présence de rats sous Altdorf, de la taille d'un humain voire plus grands. On utilise des pièges à rats pour les garder sous contrôle.</p>"
},
{
"id": "Giant Spider",
"name": "Araignée Géante",
"description": "<p>Les araignées géantes se tapissent au plus profond des forêts et des grottes de l'Empire, mais elles peuvent vivre n'importe où, y compris dans les greniers poussiéreux et les caves sombres. La plupart capturent leurs proies à l'aide des toiles solides avant de leur injecter du venin. Bien que la plupart aient la taille d'un grand rat, certaines espèces d'araignées géantes sont effroyablement grandes. Les gobelins des forêts capturent souvent de grands spécimens pour en faire des montures.</p>"
},
{
"id": "Goblin",
"name": "Gobelin",
"description": "<p>Des gobelins, sire ! Par milliers!'</p>\n<p>- Lieutenant Bromkopf, 24ème régiment de Fantassins Reiklander</p>\n<p> </p>\n<p>L'instinct de conservation d'un gobelin, rachitique, malveillant, mais également agile et intelligent, ne doit jamais être sous-estimé. Ces créatures sont néanmoins lâches et se regroupent donc facilement si le nombre leur procure un avantage écrasant. Les gobelins rejoignent souvent les armées d'orcs - pas toujours par choix - s'appropriant opportunément une partie du butin de guerre pendant que les autres prennent part au plus gros des combats.</p>"
},
{
"id": "Gor",
"name": "Gor",
"description": "<p>Les Gors, les hommes-bêtes les plus courants, hantent presque chaque forêt du Vieux Monde. Leur apparence varie considérablement, mais tous allient des traits bestiaux et humains, souvent avec la tête et les jambes d'un bouc et le torse et les bras d'un humains. La seule caractéristique commune à tous les Gors est une grande paire de cornes - les plus grandes sont les meilleures, car elles indiquent leur statut parmi les hommes-bêtes - un trait qui les différencie des Ungor et des Changepeaux. Les plus grands Gors sont connus sous le nom de Bestigors.</p>"
},
{
"id": "Griffon",
"name": "Griffon",
"description": "<p>Avec les pattes avant et les ailes d'un aigle et l'arrière-train d'un grand chat, les Griffons sont des bêtes élégantes au port naturellement altier et majestueux. Ils nichent dans les montagnes qui entourent l'Empire et sont des tueurs rapides et efficaces, peu enclins à la destruction aveugle de créatures, contrairement aux manticores ou aux hippogriffes. Peut-être à cause de cette apparente noblesse, l'image du Griffon est extraordinairement populaire au sein de l'Empire, utilisée sur les blasons, dans l'iconographie religieuse et comme symbole de l'Empire lui-même. Les Griffons sont aussi l'une des bêtes les plus intelligentes. S'ils sont capturés relativement jeunes et soumis à un entraînement approprié, ils peuvent être extrêmement loyaux, capables d'anticiper et d'obéir à une grande variété d'ordres. Ils sont tellement prisés que les chasseurs meurent régulièrement en essayant de se procurer un œufs de Griffons pour le revendre. Le Griffon le plus célèbre du Reikland est sans doute Griffe-de-Mort. Installé dans le zoo impérial d'Altdorf, Griffe-de-Mort appartenait au prince héritier du Reikland et aurait parait-il éclos dans les mains de l'empereur Karl-Franz en personne - ce qui a causé l'impression de toutes sortes de dessins humoristiques dans des pamphlets séditieux.</p>"
},
{
"id": "Hippogryph",
"name": "Hippogryffe",
"description": "<p>Particulièrement féroces et territoriaux, les hippogriffes possèdent une tête et des ailes d'aigle, le buste d'un lion, et l'arrière-train d'un cheval. Issus le plus souvent des Montagnes Grises, ils attaquent sans provocation, presque imprudents dans leur rage, déchirant la chair en lambeaux pour la dévorer plus tard. Leur rage est telle que des hardes entiers de bétail peuvent être anéantis par un seul Hippogriffe, qui s'enfuit ensuite avec ses proies préférées, abandonnant les restes aux charognards. N'étant pas foncièrement intelligents, lors de la chasse, les hippogriffes ont tendance à tuer tout ce qu'ils voient. Supposant que tout ce qui ne voient plus disparaît à jamais, les victimes réelles des hippogriffes sont relativement peu nombreuses, puisqu'il suffit de trouver une bonne cachette pour leur échapper.</p>"
},
{
"id": "Horse",
"name": "Cheval",
"description": "<p>Les chevaux sont élevés pour différents usages, servant de monture rapide aux Messagers, de destriers robustes aux Chevaliers et de chevaux de traits pour les fermiers. Ils sont tellement utilisés que le commerce de chevaux est presque une compétition sportive au Reikland. Les maquignons (les vendeurs de chevaux) sans scrupules cherchent à se faire de l'argent sur le dos d'acheteurs ignorants, en lissant la robe et limant les dents des vieux chevaux, ou en leur bourrant les naseaux de petits chiffons pour éponger le mucus. Un acheteur non averti devrait donc se montrer prudent.</p>"
},
{
"id": "Hydra",
"name": "Hydre",
"description": "<p>L'Hydre possède plusieurs têtes semblables à celles d'un lézard et un corps massif qui supporte un entrelacs de cous et de têtes qui soufflent une fumée incandescente et mordent férocement. Créature étonnamment tenace et furtive, les hydres traquent leurs proies sur des kilomètres, mais elles perdent trop souvent patience et chargent, en rugissant de toutes leurs têtes.</p>"
},
{
"id": "Jabberslythe",
"name": "Jabberslythe",
"description": "<p>Le Jabberslythe est une ancienne créature du Chaos qui se cache dans les profondeurs des forêts les plus sombres. Créature démente, le Jabberslythe est un ignoble mélange de crapaud, de dragon de vase et d'insecte, le tout irrigués de sang noir corrosif qui jaillit à la moindre blessure. Pire encore, tous ceux qui regardent cette horreur perdent l'esprit, baragouinant, se griffant les yeux et riant hystériquement, faisant d'eux des proies facile pour le Jabberslythe. De plus, il dispose d'une langue collante capable de fouetter et de ramener son prochain repas dans sa gueule béante. En accord avec son apparence, la créature se déplace de façon gauche et balourde, et possède même des ailes, bien trop petites pour réussir à son impressionnant gabarit.</p>"
},
{
"id": "Manticore",
"name": "Manticore",
"description": "<p><em>\"Quand je voyageais avec les elfes d'Ulthuan, j'ai vu beaucoup de choses qui sidéreraient la plupart des gens. Une fois, j'ai aperçu une manticore, avec au beau milieu des sa crinière de lion un visage de haut elfe ! Je suppose que c'était moins une Manticore qu'une Elfticore.\"</em></p>\n<p>- Adhemar Fitztancred, Gardien Gris, conteur et menteur</p>\n<p> Heureusement assez rares, les Manticores sont féroces à lextrême, animées par la volonté de débarrasser leur territoire, avec une extraordinaire brutalité, des prédateurs rivaux. Cela signifie que se trouver sur le territoire d'une Manticore ne passe pas inaperçu, car des corps d'autres monstres jonchent le sol. La créature possède la tête et le corps déformé d'un grand chat (bien que parfois son visage apparaisse un peu trop humain), les ailes d'une chauve-souris et une queue semblable à un fouet hérissé de barbelés.</p>"
},
{
"id": "Minotaur",
"name": "Minotaure",
"description": "<p>Les Minotaures semblables à des taureaux, énormes et massifs, dominent même les plus grands Bestigors. Les hordes possédant un grand nombre de Minotaures se considèrent comme particulièrement bénis par les Sombres Dieux. Les hommes-bêtes se rassemblent autour des Minotaures car leur présence imposante insuffle du courage même aux plus petits d'entre eux.</p>"
},
{
"id": "Mutant",
"name": "Mutant",
"description": "<p>L'un des destins les plus tragiques d'un Humain est de succomber à l'influence mutagène du Chaos. Cela peut se produire sans raison aucune, et même des bébés peuvent naître mutants. Lorsque cela se produit, beaucoup de parents ne trouvent pas la force d'assassiner leurs enfants, mais les abandonnent plutôt dans les bois, pour qu'ils meurent ou soient recueillis par d'autres mutants ou hommes-bêtes. Peu importe qu'ils soient innocents, tous les mutants sont une source de terreur pour le commun des mortels, et la plupart se tournent vers les Sombres dieux, abandonnés et aigris, ou en finissent avant qu'il ne soit trop tard.</p>"
},
{
"id": "Ogre",
"name": "Ogre",
"description": "<p><em><span class=\"fontstyle0\"><span class=\"fontstyle0\">'Oui, je les ai convoqués parce que j'avais besoin de renforts. Oui, je sais, ils mangent votre bétail. Et oui, je sais que les ogres ont un gros appétit. J'ai mis une annonce au poste de garde local, offrant une petite somme à toute personne ayant les moyens de les faire partir. Cela devrait être bien suffisant\". </span></span></em></p>\n<p><span class=\"fontstyle0\"><span class=\"fontstyle0\" style=\"font-size : 10pt ;\">- Augustus von Raushvel, Baron de Raush Vale</span> &nbsp;<br style=\"font-variant-numeric : normal ; font-variant-east-asian : normal ; hauteur de ligne : normal ; text-align : -webkit-auto ; text-size-adjust : auto ;\"></span></p>\n<p> </p>\n<p><span class=\"fontstyle0\">Grands, bruyants, brutaux et extrêmement violents, les ogres sont animés par le besoin de remplir leur énorme ventre musclé. Ils manquent d'astuce ou d'intelligence pour le faire avec subtilité, optant d'emblée pour la force dans la plupart des situations. Originaires des terres situées très à l'est, les Ogres sont répandus dans le Vieux Monde car ils adorent errer, toujours à la recherche de nouvelles proies. Au cours de leurs voyages à but alimentaires, longs de plusieurs décennies, ils mettent tout en oeuvre pour s'intégrer, portant des vêtements de la région et suivant les coutumes locales qu'ils comprennent, car cela leur permet d'attirer leur prochaine proie.</span></p>\n<p><span class=\"fontstyle0\"><br>Dans l'Empire, ils servent souvent dans l'Armée et sont souvent aperçus dans les grandes villes, où ils sont recrutés pour leurs muscles. La guilde des Vieux Halfling (des entrepreneurs en construction) s'est effectivement accaparé le travail bon marché des ogres dans nombre de villes et cités de l'Empire, source d'un grand mécontentement pour ses rivaux.</span></p>\n<p> </p>\n<h3>Halflings et ogres</h3>\n<p><span class=\"fontstyle0\">Personne ne sait pourquoi, mais les Halflings et les Ogres s'entendent bien. Beaucoup d'anciens des clans Halfling ont un ou deux gardes du corps ogres, et on dit que l'ancien de la Moot voyage rarement sans son vieil ami Zorarth Mord-Gigot, un ogre aux cheveux blancs qui vit dans l'empire depuis près d'un siècle. En retour, de nombreuses bandes de mercenaires ogres emploient des Halfling comme cuisiniers. Cependant, cette relation ne fonctionne pas toujours. Golgfag Mang'homme, le capitaine d'un groupe de mercenaire du nom de (quelle imagination !) Mang'hommes de Golgfag, employait notoirement un petit clan de Halflings aux cuisines, pour lui et ses hommes après les grandes victoires. Jusqu'à ce que Golgfag réalise que les cuisiniers avaient un bien meilleur goût que la nourriture qu'ils préparaient...</p>"
},
{
"id": "Orc",
"name": "Orc",
"description": "<p>Nous êtres les meilleurs. Nous pas êtres des foutus Gobbos verts ou des trolls stupides, nous être plus fort ! Et si quelqu'un dit que nous pas fort, nous taper dur sur foutue tête.\n<p>- Gurkk Chopecrân', Chef Orc</p>\n<p> </p>\n<p>Les Orcs sont mauvais, brutaux, belliqueux et presque immunisés contre la douleur. Ils possèdent un corps massif et de larges épaules puissantes et ne laisseront pas une chose aussi insignifiante que la perte d'un bras faire obstacle à une bonne bagarre. Ils sont taillés pour le combat et n'aiment rien de plus au monde. Quand ils n'ont pas d'ennemis à combattre, ils s'en prennent à des groupes rivaux de Peaux-vertes. S'il n'y a pas de groupe rival, ils se battront entre eux. Bien que moins nombreux que les gobelins, ils sont plus grands et plus robustes, et toutes les occasions sont bonnes pour le faire savoir. Les Orcs peuvent atteindre des tailles prodigieuses, et les plus grands, les plus forts, les plus robustes et les plus agressifs d'entre eux se voient par conséquent accorder plus de prestige : la force entraîne le pouvoir dans leur société guerrière. Certains Orcs chevauchent d'énormes sangliers pendant les batailles, un spectacle assez terrifiant.</p>"
},
{
"id": "Pegasus",
"name": "Pégase",
"description": "<p>Les Pégases sont de magnifiques chevaux blancs pourvus d'ailes de cygne. Ce sont des voltigeurs acharnés et inépuisables qui se déplacent en grandes hordes dans les haute montagne, semblant prendre beaucoup de plaisir à virevolter dans les courants d'air chauds tourbillonnants. Les pégases font de parfaits destriers et beaucoup de guerriers ou d'éclaireurs ont tenté d'en capturer un. Ils sont très intelligents, et certains pensent qu'ils ne se laissent capturer que s'ils le veulent bien, ce qui a fait naître toutes sortes de légendes romantiques fantaisistes insistant sur le fait que seuls les plus dignes ou les plus vertueux pouvaient être choisis.</p>"
},
{
"id": "Pigeon",
"name": "Pigeon",
"description": "<p>Les pigeons sont élevés dans tout le Reikland pour transporter des messages de toutes sortes. Récemment, ils ont gagné en renommé pour le transport de chargements plus mortels, car l'oiseau facilement accessible est devenu populaire parmi les ingénieurs, qui les utilisent pour déployer leurs \"bombes-pigeons\" à des degrés divers de réussite.</p>"
},
{
"id": "Rat Ogre",
"name": "Rat Ogre",
"description": "<p>Les Rats Ogres sont des brutes imposantes, élevées dans les cavernes sombres de l'Empire souterrain par les Maîtres corrupteurs du Clan Molder. Ils sont stupides, mais intrépides et implacables au combat lorsqu'ils sont dirigés par leurs maîtres Skavens. Rarement rencontrés seuls, ils ont tendance à accompagner et servir de gardes du corps à des prophètes gris, ou autres Skavens de haut rang.</p>"
},
{
"id": "Skeleton",
"name": "Squelette",
"description": "<p>Les squelettes sont les restes décharnés de ceux qui sont morts depuis longtemps, réanimés, parodies de vivants, par une magie sombre pour arpenter la terre. Ceux qui sont morts sans recevoir le repos accordé par les rituels de Morr, le Dieu de la mort, peuvent être ressuscités sous cette forme par un nécromancien suffisamment puissant. Totalement dépourvus d'intelligence, les squelettes combattent jusqu'à ce que leurs os soient réduits en miettes. Leur ténacité n'a rien à voir avec du courage, ils n'ont tout simplement peur de rien et ne peuvent pas être tués puisqu'ils ne sont vivants.</p>\n<h3>Options : Des Os</h3>\n<p>Pour Refléter la nature osseuse des Squelettes, vous pouvez imposer une pénalité de -1 Dégâts aux blessures infligées par les armes n'ayant pas le trait Assommante.</p>"
},
{
"id": "Slenderthigh Whiptongue",
"name": "Slenderthigh Whiptongue"
},
{
"id": "Snake",
"name": "Serpent",
"description": "<p>On trouve des serpents dans tout l'Empire, en particulier dans les forêts profondes. La plupart sont inoffensifs, mais certains utilisent un venin mortel ou enserrent leurs victimes jusqu'à ce qu'elles meurent par suffocation. Comme beaucoup de créatures du Vieux Monde, ils peuvent atteindre des proportions gigantesques, représentant un réel danger même pour le plus fort des mercenaires.</p>"
},
{
"id": "Snotling",
"name": "Snotling",
"description": "<p>Créatures écervelées, s'apparentant à des chiots excités et incontrôlables, les Snotlings sont des charognards et des imitateurs-nés, ramassant des os et des choses brillantes partout où ils passent, ou copiant les actions de tout ce qu'ils voient. Si les gobelins ou les orcs les poussent au conflits, ils se battent en nuées nauséabondes, essayant de submerger leurs ennemis par le poids du nombre. Pour cela, ils trouvent toutes sortes de substances répugnantes et toxiques, comme des champignons vénéneux et des déjections, à lancer sur leurs ennemis.</p>"
},
{
"id": "Stormvermin",
"name": "Vermine de choc",
"description": "<p>Les combattants d'élite des Skavens sont les Vermines de choc : plus grands, plus forts, plus endurcis et plus disciplinés que les Guerrier des clans. Ils servent de base à tout assaut majeur et constituent les gardes du corps de Skavens importants. Les Vermines de chocs sont généralement lourdement et cuirassées, arborant les combinaisons d'armes favorites de leur clan.</p>"
},
{
"id": "Tomb Banshee",
"name": "Banshee",
"description": "<p>Les Banshees sont les vestiges spectraux de sorcières autrefois puissantes dont les esprits sont imprégnés de l'énergie fétide de Dhar. Leur après-vie sans repos est tourmentée par le chagrin et l'amertume, un vide béant dans leurs âmes qui les pousse à émettre des hurlements déchirants, assez terrifiants et puissants pour plonger dans la folie ceux qui l'entendent, ou même arrêter leur cœur.</p>"
},
{
"id": "Troll",
"name": "Troll",
"description": "<p>Les trolls sont des créatures sales et immondes qui infestent tous les coins du Vieux Monde. Prompts à s'adapter à leur environnement, quel que soit leur type, ils sont tous énormes et imposants. Sombres et mus par leurs pulsions alimentaires, ils aiment tout de même amasser des biens et leurs repaires peuvent renfermer des trésors composés d'objets utiles et de valeur... et des restes macabres de leurs repas. Bien qu'il existe beaucoup d'espèces de Trolls, ils partagent tous des traits communs : ils sont généralement extrêmement stupides, ce qui signifie que tout ennemi à l'esprit vif d'esprit peut avoir un avantage sur eux ; ils peuvent régénérer, ce qui les rend extrêmement difficiles à tuer ; et ils sont capables de régurgiter leur dernier repas à volonté, en vomissant de la bile âcre sur une distance particulièrement impressionnantes - bien qu'ils répugnent à le faire, car la faim les tenaille alors.</p>"
},
{
"id": "Ungor",
"name": "Ungor",
"description": "<p>Les Ungors possèdent des cornes vestigiales ou très courtes, et il sont donc à peine considérés comme des \"Gor\" par le reste de la horde. Certains possèdent même des visages semblables à ceux des humains, faisant d'eux des infiltrés efficace, mais aussi des cibles faciles aux moqueries. En effet, les Ungors sont durement traités par les Gors, et sont souvent chétifs et mal nourris par rapport à leurs frères aux plus grandes cornes, ce qui en fait des créatures aigries, enclines à reporter leur jalousie sur les autres.</p>"
},
{
"id": "Vampire",
"name": "Vampire",
"description": "<p>Les vampires se considèrent comme les rois de la nuit. Beaucoup peuvent passer pour des humains, certains opérant même durant de longues périodes parmi les vivants. Malgré leur apparence extérieure d'humains, aucun coeur ne bat sous leur peau pâle, et au lieu des faims des mortels, ils souffrent d'une soif incessante de sang. Tous les vampires du Vieux Monde sont en fin de compte tous issus de lignées anciennes, apparues il y a des millénaires au Sud. Beaucoup sont extrêmement fiers de leur héritage, des traits et traditions qui les différencient des autres. Les vampires de lignées différentes sont souvent de grands rivaux, mais ils sont assez intelligents pour se rassembler quand cela devient nécessaire pour faire face aux ennemis les plus importants.</p>"
},
{
"id": "Varghulf",
"name": "Chauve-Souris Vampire",
"description": "<p>La plupart des vampires dissimulent leur besoin de sang sous une apparence de civilité et de savoir-vivre, se définissant eux-même comme les aristocrates des morts-vivants. Cependant, certains fuient leur côté humain et embrassent la bête qui les habitent. Ces Chauve-Souris Vampires sont des créatures cruelles et sauvages, abandonnées à l'autosatisfaction animale. Elles se manifestent sous la forme de grandes bêtes aux allures de chauve-souris, se délectant de leur besoin primaire de sang.</p>"
},
{
"id": "Wolf",
"name": "Loup",
"description": "<p>Les loups chassent généralement en meute et ont la réputation d'être des chasseurs tenaces, poursuivant leurs proies sur des dizaines de kilomètres. Plusieurs espèces rôdent dans le Reikland, dont les redoutables loups géants, qui sont capturés et élevés par les gobelins qui les utilisent comme gardes ou montures.</p>"
},
{
"id": "Wyvern",
"name": "Vouivre",
"description": "Bien que les incultes les confondent souvent à tort avec des dragons, au-delà de quelques similitudes superficielles, les vouivres nauséabondes n'ont rien de commun avec ces dignes créatures. Charognards lâches et malvoyants, elles ont tendance à assouvir leur énorme appétit en choisissant des créatures sans défense - principalement des moutons et des chèvres -, évitant autant que possible le combat direct. Contrairement aux autres monstres des Montagnes, les vouivres ne sont pas particulièrement territoriales, et se déplacent généralement lorsque leurs territoires de chasse sont envahis."
},
{
"id": "Zombie",
"name": "Zombie",
"description": "<p>Comme les squelettes, les zombies sont des créatures mortes-vivantes réanimées et maintenues \"en vie\" par d'ignobles magies. Contrairement aux Squelettes, ils sont morts si récemment qu'une grande partie de leur corps subsiste encore, la chair malade et pourrissante s'affaissant sur leurs organes boursouflés et envahis par les vers. Quand ils combattent, leur chair et leurs organes se détachent de leurs os, libérant des effluves nauséabonde et toxiques, suffisamment puissantes pour retourner l'estomac de quiconque, à part, peut être, les soldats les plus endurcis.</p>"
}
]
}

View File

@ -0,0 +1,29 @@
package.path = package.path .. ";luajson/?.lua"
local JSON = require"json"
local beastref_f = "../compendium/wfrp4e.bestiary.json"
local beastdescr_f = "compendium_wfrp4e.bestiary.json"
local f1 = io.open(beastref_f)
local strjson = f1:read("*a")
local beastref = JSON.decode(strjson)
local f2 = io.open(beastdescr_f)
strjson = f2:read("*a")
f2:close()
local beastdescr = JSON.decode(strjson)
for _, beasttext in pairs(beastdescr.entries) do
for _, beastgood in pairs(beastref.entries) do
if beasttext.id == beastgood.id then
beastgood.description = beasttext.description
end
end
end
local jsonout = JSON.encode( beastref )
local fout = io.open("beasts.json", "w+")
fout:write( jsonout )
fout:close()

625
tools/lpeg/lpcap.lua Normal file
View File

@ -0,0 +1,625 @@
--[[
LPEGLJ
lpcap.lua
Capture functions
Copyright (C) 2014 Rostislav Sacek.
based on LPeg v1.0 - PEG pattern matching for Lua
Lua.org & PUC-Rio written by Roberto Ierusalimschy
http://www.inf.puc-rio.br/~roberto/lpeg/
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
--]]
local ffi = require "ffi"
local Cclose = 0
local Cposition = 1
local Cconst = 2
local Cbackref = 3
local Carg = 4
local Csimple = 5
local Ctable = 6
local Cfunction = 7
local Cquery = 8
local Cstring = 9
local Cnum = 10
local Csubst = 11
local Cfold = 12
local Cruntime = 13
local Cgroup = 14
local MAXSTRCAPS = 10
local pushcapture
local addonestring
-- Goes back in a list of captures looking for an open capture
-- corresponding to a close
local function findopen(cs, index)
local n = 0; -- number of closes waiting an open
while true do
index = index - 1
if cs.ocap[index].kind == Cclose then
n = n + 1 -- one more open to skip
elseif cs.ocap[index].siz == 0 then
if n == 0 then
return index
end
n = n - 1
end
end
end
local function checknextcap(cs, captop)
local cap = cs.cap;
-- not a single capture? ((cap)->siz != 0)
if cs.ocap[cap].siz == 0 then
local n = 0; -- number of opens waiting a close
-- look for corresponding close
while true do
cap = cap + 1
if cap > captop then return end
if cs.ocap[cap].kind == Cclose then
n = n - 1
if n + 1 == 0 then
break;
end
elseif cs.ocap[cap].siz == 0 then
n = n + 1
end
end
end
cap = cap + 1; -- + 1 to skip last close (or entire single capture)
if cap > captop then return end
return true
end
-- Go to the next capture
local function nextcap(cs)
local cap = cs.cap;
-- not a single capture? ((cap)->siz != 0)
if cs.ocap[cap].siz == 0 then
local n = 0; -- number of opens waiting a close
-- look for corresponding close
while true do
cap = cap + 1
if cs.ocap[cap].kind == Cclose then
n = n - 1
if n + 1 == 0 then
break;
end
elseif cs.ocap[cap].siz == 0 then
n = n + 1
end
end
end
cs.cap = cap + 1; -- + 1 to skip last close (or entire single capture)
end
-- Push on the Lua stack all values generated by nested captures inside
-- the current capture. Returns number of values pushed. 'addextra'
-- makes it push the entire match after all captured values. The
-- entire match is pushed also if there are no other nested values,
-- so the function never returns zero.
local function pushnestedvalues(cs, addextra, out, valuetable)
local co = cs.cap
cs.cap = cs.cap + 1
-- no nested captures?
if cs.ocap[cs.cap - 1].siz ~= 0 then
local st = cs.ocap[co].s
local l = cs.ocap[co].siz - 1
out.outindex = out.outindex + 1
out.out[out.outindex] = cs.s and cs.s:sub(st, st + l - 1) or cs.stream(st, st + l - 1)
return 1; -- that is it
else
local n = 0;
while cs.ocap[cs.cap].kind ~= Cclose do -- repeat for all nested patterns
n = n + pushcapture(cs, out, valuetable);
end
-- need extra?
if addextra or n == 0 then
local st = cs.ocap[co].s
local l = cs.ocap[cs.cap].s - cs.ocap[co].s
out.outindex = out.outindex + 1
out.out[out.outindex] = cs.s and cs.s:sub(st, st + l - 1) or cs.stream(st, st + l - 1)
n = n + 1
end
cs.cap = cs.cap + 1 -- skip close entry
return n;
end
end
-- Push only the first value generated by nested captures
local function pushonenestedvalue(cs, out, valuetable)
local n = pushnestedvalues(cs, false, out, valuetable)
for i = n, 2, -1 do
out.out[out.outindex] = nil
out.outindex = out.outindex - 1
end
end
-- Try to find a named group capture with the name given at the top of
-- the stack; goes backward from 'cap'.
local function findback(cs, cap, name, valuetable)
-- repeat until end of list
while cap > 0 do
cap = cap - 1
local continue
if cs.ocap[cap].kind == Cclose then
cap = findopen(cs, cap); -- skip nested captures
elseif cs.ocap[cap].siz == 0 then
continue = true -- opening an enclosing capture: skip and get previous
end
if not continue and cs.ocap[cap].kind == Cgroup and cs.ocap[cap].idx ~= 0 then
local gname = valuetable[cs.ocap[cap].idx] -- get group name
-- right group?
if name == gname then
return cap;
end
end
end
error(("back reference '%s' not found"):format(name), 0)
end
-- Back-reference capture. Return number of values pushed.
local function backrefcap(cs, out, valuetable)
local curr = cs.cap;
local name = valuetable[cs.ocap[cs.cap].idx] -- reference name
cs.cap = findback(cs, curr, name, valuetable) -- find corresponding group
local n = pushnestedvalues(cs, false, out, valuetable); -- push group's values
cs.cap = curr + 1;
return n;
end
-- Table capture: creates a new table and populates it with nested
-- captures.
local function tablecap(cs, out, valuetable)
local n = 0;
local t = {}
cs.cap = cs.cap + 1
-- table is empty
if cs.ocap[cs.cap - 1].siz == 0 then
while cs.ocap[cs.cap].kind ~= Cclose do
local subout = { outindex = 0, out = {} }
-- named group?
if cs.ocap[cs.cap].kind == Cgroup and cs.ocap[cs.cap].idx ~= 0 then
local groupname = valuetable[cs.ocap[cs.cap].idx] -- push group name
pushonenestedvalue(cs, subout, valuetable)
t[groupname] = subout.out[1]
else
-- not a named group
local k = pushcapture(cs, subout, valuetable)
-- store all values into table
for i = 1, subout.outindex do
t[i + n] = subout.out[i]
end
n = n + k;
end
end
cs.cap = cs.cap + 1 -- skip close entry
end
out.outindex = out.outindex + 1
out.out[out.outindex] = t
return 1; -- number of values pushed (only the table)
end
-- Table-query capture
local function querycap(cs, out, valuetable)
local table = valuetable[cs.ocap[cs.cap].idx]
local subout = { outindex = 0, out = {} }
pushonenestedvalue(cs, subout, valuetable) -- get nested capture
-- query cap. value at table
if table[subout.out[1]] ~= nil then
out.outindex = out.outindex + 1
out.out[out.outindex] = table[subout.out[1]]
return 1
end
return 0
end
-- Fold capture
local function foldcap(cs, out, valuetable)
local fce = valuetable[cs.ocap[cs.cap].idx]
cs.cap = cs.cap + 1
-- no nested captures?
-- or no nested captures (large subject)?
if cs.ocap[cs.cap - 1].siz ~= 0 or
cs.ocap[cs.cap].kind == Cclose then
error("no initial value for fold capture", 0);
end
local subout = { outindex = 0; out = {} }
local n = pushcapture(cs, subout, valuetable) -- nested captures with no values?
if n == 0 then
error("no initial value for fold capture", 0);
end
local acumulator = subout.out[1] -- leave only one result for accumulator
while cs.ocap[cs.cap].kind ~= Cclose do
local subout = { outindex = 0; out = {} }
n = pushcapture(cs, subout, valuetable); -- get next capture's values
acumulator = fce(acumulator, unpack(subout.out, 1, subout.outindex)) -- call folding function
end
cs.cap = cs.cap + 1; -- skip close entry
out.outindex = out.outindex + 1
out.out[out.outindex] = acumulator
return 1; -- only accumulator left on the stack
end
local function retcount(...)
return select('#', ...), { ... }
end
-- Function capture
local function functioncap(cs, out, valuetable)
local fce = valuetable[cs.ocap[cs.cap].idx] -- push function
local subout = { outindex = 0, out = {} }
local n = pushnestedvalues(cs, false, subout, valuetable); -- push nested captures
local count, ret = retcount(fce(unpack(subout.out, 1, n))) -- call function
for i = 1, count do
out.outindex = out.outindex + 1
out.out[out.outindex] = ret[i]
end
return count
end
-- Select capture
local function numcap(cs, out, valuetable)
local idx = valuetable[cs.ocap[cs.cap].idx] -- value to select
-- no values?
if idx == 0 then
nextcap(cs); -- skip entire capture
return 0; -- no value produced
else
local subout = { outindex = 0, out = {} }
local n = pushnestedvalues(cs, false, subout, valuetable)
-- invalid index?
if n < idx then
error(("no capture '%d'"):format(idx), 0)
else
out.outindex = out.outindex + 1
out.out[out.outindex] = subout.out[idx] -- get selected capture
return 1;
end
end
end
-- Calls a runtime capture. Returns number of captures removed by
-- the call, including the initial Cgroup. (Captures to be added are
-- on the Lua stack.)
local function runtimecap(cs, close, s, out, valuetable)
local open = findopen(cs, close)
assert(cs.ocap[open].kind == Cgroup)
cs.ocap[close].kind = Cclose; -- closes the group
cs.ocap[close].s = s;
cs.cap = open;
local fce = valuetable[cs.ocap[cs.cap].idx] -- push function to be called
local subout = { outindex = 0, out = {} }
local n = pushnestedvalues(cs, false, subout, valuetable); -- push nested captures
local count, ret = retcount(fce(cs.s or cs.stream, s, unpack(subout.out, 1, n))) -- call dynamic function
for i = 1, count do
out.outindex = out.outindex + 1
out.out[out.outindex] = ret[i]
end
return close - open -- number of captures of all kinds removed
end
-- Collect values from current capture into array 'cps'. Current
-- capture must be Cstring (first call) or Csimple (recursive calls).
-- (In first call, fills %0 with whole match for Cstring.)
-- Returns number of elements in the array that were filled.
local function getstrcaps(cs, cps, n)
local k = n
n = n + 1
cps[k + 1].isstring = true; -- get string value
cps[k + 1].startstr = cs.ocap[cs.cap].s; -- starts here
cs.cap = cs.cap + 1
-- nested captures?
if cs.ocap[cs.cap - 1].siz == 0 then
-- traverse them
while cs.ocap[cs.cap].kind ~= Cclose do
-- too many captures?
if n >= MAXSTRCAPS then
nextcap(cs); -- skip extra captures (will not need them)
elseif cs.ocap[cs.cap].kind == Csimple then
-- string?
n = getstrcaps(cs, cps, n); -- put info. into array
else
cps[n + 1].isstring = false; -- not a string
cps[n + 1].origcap = cs.cap; -- keep original capture
nextcap(cs);
n = n + 1;
end
end
cs.cap = cs.cap + 1 -- skip close
end
cps[k + 1].endstr = cs.ocap[cs.cap - 1].s + cs.ocap[cs.cap - 1].siz - 1 -- ends here
return n;
end
-- add next capture value (which should be a string) to buffer 'b'
-- String capture: add result to buffer 'b' (instead of pushing
-- it into the stack)
local function stringcap(cs, b, valuetable)
local cps = {}
for i = 1, MAXSTRCAPS do
cps[#cps + 1] = {}
end
local fmt = valuetable[cs.ocap[cs.cap].idx]
local n = getstrcaps(cs, cps, 0) - 1; -- collect nested captures
local i = 1
-- traverse them
while i <= #fmt do
local c = fmt:sub(i, i)
-- not an escape?
if c ~= '%' then
b[#b + 1] = c -- add it to buffer
elseif fmt:sub(i + 1, i + 1) < '0' or fmt:sub(i + 1, i + 1) > '9' then
-- not followed by a digit?
i = i + 1
b[#b + 1] = fmt:sub(i, i)
else
i = i + 1
local l = fmt:sub(i, i) - '0'; -- capture index
if l > n then
error(("invalid capture index (%d)"):format(l), 0)
elseif cps[l + 1].isstring then
b[#b + 1] = cs.s and cs.s:sub(cps[l + 1].startstr, cps[l + 1].endstr - cps[l + 1].startstr + cps[l + 1].startstr - 1) or
cs.stream(cps[l + 1].startstr, cps[l + 1].endstr - cps[l + 1].startstr + cps[l + 1].startstr - 1)
else
local curr = cs.cap;
cs.cap = cps[l + 1].origcap; -- go back to evaluate that nested capture
if not addonestring(cs, b, "capture", valuetable) then
error(("no values in capture index %d"):format(l), 0)
end
cs.cap = curr; -- continue from where it stopped
end
end
i = i + 1
end
end
-- Substitution capture: add result to buffer 'b'
local function substcap(cs, b, valuetable)
local curr = cs.ocap[cs.cap].s;
-- no nested captures?
if cs.ocap[cs.cap].siz ~= 0 then
-- keep original text
b[#b + 1] = cs.s and cs.s:sub(curr, cs.ocap[cs.cap].siz - 1 + curr - 1) or
cs.stream(curr, cs.ocap[cs.cap].siz - 1 + curr - 1)
else
cs.cap = cs.cap + 1 -- skip open entry
-- traverse nested captures
while cs.ocap[cs.cap].kind ~= Cclose do
local next = cs.ocap[cs.cap].s;
b[#b + 1] = cs.s and cs.s:sub(curr, next - curr + curr - 1) or
cs.stream(curr, next - curr + curr - 1) -- add text up to capture
if addonestring(cs, b, "replacement", valuetable) then
curr = cs.ocap[cs.cap - 1].s + cs.ocap[cs.cap - 1].siz - 1; -- continue after match
else
-- no capture value
curr = next; -- keep original text in final result
end
end
b[#b + 1] = cs.s and cs.s:sub(curr, curr + cs.ocap[cs.cap].s - curr - 1) or
cs.stream(curr, curr + cs.ocap[cs.cap].s - curr - 1) -- add last piece of text
end
cs.cap = cs.cap + 1 -- go to next capture
end
-- Evaluates a capture and adds its first value to buffer 'b'; returns
-- whether there was a value
function addonestring(cs, b, what, valuetable)
local tag = cs.ocap[cs.cap].kind
if tag == Cstring then
stringcap(cs, b, valuetable); -- add capture directly to buffer
return 1
elseif tag == Csubst then
substcap(cs, b, valuetable); -- add capture directly to buffer
return 1
else
local subout = { outindex = 0, out = {} }
local n = pushcapture(cs, subout, valuetable);
if n > 0 then
if type(subout.out[1]) ~= 'string' and type(subout.out[1]) ~= 'number' then
error(("invalid %s value (a %s)"):format(what, type(subout.out[1])), 0)
end
b[#b + 1] = subout.out[1]
return n
end
end
end
-- Push all values of the current capture into the stack; returns
-- number of values pushed
function pushcapture(cs, out, valuetable)
local type = cs.ocap[cs.cap].kind
if type == Cposition then
out.outindex = out.outindex + 1
out.out[out.outindex] = cs.ocap[cs.cap].s
cs.cap = cs.cap + 1;
return 1;
elseif type == Cconst then
out.outindex = out.outindex + 1
out.out[out.outindex] = valuetable[cs.ocap[cs.cap].idx]
cs.cap = cs.cap + 1
return 1;
elseif type == Carg then
local arg = valuetable[cs.ocap[cs.cap].idx]
cs.cap = cs.cap + 1
if arg > cs.ptopcount then
error(("reference to absent extra argument #%d"):format(arg), 0)
end
out.outindex = out.outindex + 1
out.out[out.outindex] = cs.ptop[arg]
return 1;
elseif type == Csimple then
local k = pushnestedvalues(cs, true, out, valuetable)
local index = out.outindex
table.insert(out.out, index - k + 1, out.out[index])
out[index + 1] = nil
return k;
elseif type == Cruntime then
out.outindex = out.outindex + 1
out.out[out.outindex] = valuetable[cs.ocap[cs.cap].idx]
cs.cap = cs.cap + 1;
return 1;
elseif type == Cstring then
local b = {}
stringcap(cs, b, valuetable)
out.outindex = out.outindex + 1
out.out[out.outindex] = table.concat(b)
return 1;
elseif type == Csubst then
local b = {}
substcap(cs, b, valuetable);
out.outindex = out.outindex + 1
out.out[out.outindex] = table.concat(b)
return 1;
elseif type == Cgroup then
-- anonymous group?
if cs.ocap[cs.cap].idx == 0 then
return pushnestedvalues(cs, false, out, valuetable); -- add all nested values
else
-- named group: add no values
nextcap(cs); -- skip capture
return 0
end
elseif type == Cbackref then
return backrefcap(cs, out, valuetable)
elseif type == Ctable then
return tablecap(cs, out, valuetable)
elseif type == Cfunction then
return functioncap(cs, out, valuetable)
elseif type == Cnum then
return numcap(cs, out, valuetable)
elseif type == Cquery then
return querycap(cs, out, valuetable)
elseif type == Cfold then
return foldcap(cs, out, valuetable)
else
assert(false)
end
end
-- Prepare a CapState structure and traverse the entire list of
-- captures in the stack pushing its results. 's' is the subject
-- string, 'r' is the final position of the match, and 'ptop'
-- the index in the stack where some useful values were pushed.
-- Returns the number of results pushed. (If the list produces no
-- results, push the final position of the match.)
local function getcaptures(capture, s, stream, r, valuetable, ...)
local n = 0;
local cs = { cap = 0 }
local out = { outindex = 0; out = {} }
-- is there any capture?
if capture[cs.cap].kind ~= Cclose then
cs.ocap = capture
cs.s = s;
cs.stream = stream
cs.ptopcount, cs.ptop = retcount(...)
repeat -- collect their values
n = n + pushcapture(cs, out, valuetable)
until cs.ocap[cs.cap].kind == Cclose
end
-- no capture values?
if n == 0 then
if not r then
return
else
return r
end
end
assert(out.outindex < 7998, "(too many captures)")
return unpack(out.out, 1, out.outindex)
end
local function getcapturesruntime(capture, s, stream, notdelete, min, max, captop, valuetable, ...)
local n = 0;
local cs = { cap = min }
local out = { outindex = 0; out = {} }
cs.ocap = capture
cs.s = s
cs.stream = stream
cs.ptopcount, cs.ptop = retcount(...)
local start = 0
repeat -- collect their values
if not checknextcap(cs, max) then break end
local notdelete = notdelete or capture[cs.cap].kind == Cgroup and capture[cs.cap].idx ~= 0 and capture[cs.cap].candelete == 0
pushcapture(cs, out, valuetable)
if notdelete then
start = cs.cap
else
n = n + cs.cap - start
for i = 0, captop - cs.cap - 1 do
ffi.copy(capture + start + i, capture + cs.cap + i, ffi.sizeof('CAPTURE'))
end
max = max - (cs.cap - start)
captop = captop - (cs.cap - start)
cs.cap = start
end
until cs.cap == max
assert(out.outindex < 7998, "(too many captures)")
return n, out.out, out.outindex
end
return {
getcaptures = getcaptures,
runtimecap = runtimecap,
getcapturesruntime = getcapturesruntime,
}

1057
tools/lpeg/lpcode.lua Normal file

File diff suppressed because it is too large Load Diff

1373
tools/lpeg/lpeg.lua Normal file

File diff suppressed because it is too large Load Diff

356
tools/lpeg/lpprint.lua Normal file
View File

@ -0,0 +1,356 @@
--[[
LPEGLJ
lpprint.lua
Tree, code and debug print function (only for debuging)
Copyright (C) 2014 Rostislav Sacek.
based on LPeg v1.0 - PEG pattern matching for Lua
Lua.org & PUC-Rio written by Roberto Ierusalimschy
http://www.inf.puc-rio.br/~roberto/lpeg/
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
--]]
local ffi = require"ffi"
local band, rshift, lshift = bit.band, bit.rshift, bit.lshift
ffi.cdef[[
int isprint ( int c );
]]
local RuleLR = 0x10000
local Ruleused = 0x20000
-- {======================================================
-- Printing patterns (for debugging)
-- =======================================================
local TChar = 0
local TSet = 1
local TAny = 2 -- standard PEG elements
local TTrue = 3
local TFalse = 4
local TRep = 5
local TSeq = 6
local TChoice = 7
local TNot = 8
local TAnd = 9
local TCall = 10
local TOpenCall = 11
local TRule = 12 -- sib1 is rule's pattern, sib2 is 'next' rule
local TGrammar = 13 -- sib1 is initial (and first) rule
local TBehind = 14 -- match behind
local TCapture = 15 -- regular capture
local TRunTime = 16 -- run-time capture
local IAny = 0 -- if no char, fail
local IChar = 1 -- if char != aux, fail
local ISet = 2 -- if char not in val, fail
local ITestAny = 3 -- in no char, jump to 'offset'
local ITestChar = 4 -- if char != aux, jump to 'offset'
local ITestSet = 5 -- if char not in val, jump to 'offset'
local ISpan = 6 -- read a span of chars in val
local IBehind = 7 -- walk back 'aux' characters (fail if not possible)
local IRet = 8 -- return from a rule
local IEnd = 9 -- end of pattern
local IChoice = 10 -- stack a choice; next fail will jump to 'offset'
local IJmp = 11 -- jump to 'offset'
local ICall = 12 -- call rule at 'offset'
local IOpenCall = 13 -- call rule number 'offset' (must be closed to a ICall)
local ICommit = 14 -- pop choice and jump to 'offset'
local IPartialCommit = 15 -- update top choice to current position and jump
local IBackCommit = 16 -- "fails" but jump to its own 'offset'
local IFailTwice = 17 -- pop one choice and then fail
local IFail = 18 -- go back to saved state on choice and jump to saved offset
local IGiveup = 19 -- internal use
local IFullCapture = 20 -- complete capture of last 'off' chars
local IOpenCapture = 21 -- start a capture
local ICloseCapture = 22
local ICloseRunTime = 23
local Cclose = 0
local Cposition = 1
local Cconst = 2
local Cbackref = 3
local Carg = 4
local Csimple = 5
local Ctable = 6
local Cfunction = 7
local Cquery = 8
local Cstring = 9
local Cnum = 10
local Csubst = 11
local Cfold = 12
local Cruntime = 13
local Cgroup = 14
-- number of siblings for each tree
local numsiblings = {
[TRep] = 1,
[TSeq] = 2,
[TChoice] = 2,
[TNot] = 1,
[TAnd] = 1,
[TRule] = 2,
[TGrammar] = 1,
[TBehind] = 1,
[TCapture] = 1,
[TRunTime] = 1,
}
local names = {
[IAny] = "any",
[IChar] = "char",
[ISet] = "set",
[ITestAny] = "testany",
[ITestChar] = "testchar",
[ITestSet] = "testset",
[ISpan] = "span",
[IBehind] = "behind",
[IRet] = "ret",
[IEnd] = "end",
[IChoice] = "choice",
[IJmp] = "jmp",
[ICall] = "call",
[IOpenCall] = "open_call",
[ICommit] = "commit",
[IPartialCommit] = "partial_commit",
[IBackCommit] = "back_commit",
[IFailTwice] = "failtwice",
[IFail] = "fail",
[IGiveup] = "giveup",
[IFullCapture] = "fullcapture",
[IOpenCapture] = "opencapture",
[ICloseCapture] = "closecapture",
[ICloseRunTime] = "closeruntime"
}
local function printcharset(st)
io.write("[");
local i = 0
while i <= 255 do
local first = i;
while band(st[rshift(i, 5)], lshift(1, band(i, 31))) ~= 0 and i <= 255 do
i = i + 1
end
if i - 1 == first then -- unary range?
io.write(("(%02x)"):format(first))
elseif i - 1 > first then -- non-empty range?
io.write(("(%02x-%02x)"):format(first, i - 1))
end
i = i + 1
end
io.write("]")
end
local modes = {
[Cclose] = "close",
[Cposition] = "position",
[Cconst] = "constant",
[Cbackref] = "backref",
[Carg] = "argument",
[Csimple] = "simple",
[Ctable] = "table",
[Cfunction] = "function",
[Cquery] = "query",
[Cstring] = "string",
[Cnum] = "num",
[Csubst] = "substitution",
[Cfold] = "fold",
[Cruntime] = "runtime",
[Cgroup] = "group"
}
local function printcapkind(kind)
io.write(("%s"):format(modes[kind]))
end
local function printjmp(p, index)
io.write(("-> %d"):format(index + p[index].offset))
end
local function printrulename(p, index, rulenames)
if rulenames and rulenames[index + p[index].offset] then
io.write(' ', rulenames[index + p[index].offset])
end
end
local function printinst(p, index, valuetable, rulenames)
local code = p[index].code
if rulenames and rulenames[index] then
io.write(rulenames[index], '\n')
end
io.write(("%04d: %s "):format(index, names[code]))
if code == IChar then
io.write(("'%s'"):format(string.char(p[index].val)))
elseif code == ITestChar then
io.write(("'%s'"):format(string.char(p[index].val)))
printjmp(p, index)
printrulename(p, index, rulenames)
elseif code == IFullCapture then
printcapkind(band(p[index].val, 0x0f));
io.write((" (size = %d) (idx = %s)"):format(band(rshift(p[index].val, 4), 0xF), tostring(valuetable[p[index].offset])))
elseif code == IOpenCapture then
printcapkind(band(p[index].val, 0x0f))
io.write((" (idx = %s)"):format(tostring(valuetable[p[index].offset])))
elseif code == ISet then
printcharset(valuetable[p[index].val]);
elseif code == ITestSet then
printcharset(valuetable[p[index].val])
printjmp(p, index);
printrulename(p, index, rulenames)
elseif code == ISpan then
printcharset(valuetable[p[index].val]);
elseif code == IOpenCall then
io.write(("-> %d"):format(p[index].offset))
elseif code == IBehind then
io.write(("%d"):format(p[index].val))
elseif code == IJmp or code == ICall or code == ICommit or code == IChoice or
code == IPartialCommit or code == IBackCommit or code == ITestAny then
printjmp(p, index);
if (code == ICall or code == IJmp) and p[index].aux > 0 then
io.write(' ', valuetable[p[index].aux])
else
printrulename(p, index, rulenames)
end
end
io.write("\n")
end
local function printpatt(p, valuetable)
local ruleNames = {}
for i = 0, p.size - 1 do
local code = p.p[i].code
if (code == ICall or code == IJmp) and p.p[i].aux > 0 then
local index = i + p.p[i].offset
ruleNames[index] = valuetable[p.p[i].aux]
end
end
for i = 0, p.size - 1 do
printinst(p.p, i, valuetable, ruleNames)
end
end
local function printcap(cap, index, valuetable)
printcapkind(cap[index].kind)
io.write((" (idx: %s - size: %d) -> %d\n"):format(valuetable[cap[index].idx], cap[index].siz, cap[index].s))
end
local function printcaplist(cap, limit, valuetable)
io.write(">======\n")
local index = 0
while cap[index].s and index < limit do
printcap(cap, index, valuetable)
index = index + 1
end
io.write("=======\n")
end
-- ======================================================
-- {======================================================
-- Printing trees (for debugging)
-- =======================================================
local tagnames = {
[TChar] = "char",
[TSet] = "set",
[TAny] = "any",
[TTrue] = "true",
[TFalse] = "false",
[TRep] = "rep",
[TSeq] = "seq",
[TChoice] = "choice",
[TNot] = "not",
[TAnd] = "and",
[TCall] = "call",
[TOpenCall] = "opencall",
[TRule] = "rule",
[TGrammar] = "grammar",
[TBehind] = "behind",
[TCapture] = "capture",
[TRunTime] = "run-time"
}
local function printtree(tree, ident, index, valuetable)
for i = 1, ident do
io.write(" ")
end
local tag = tree[index].tag
io.write(("%s"):format(tagnames[tag]))
if tag == TChar then
local c = tree[index].val
if ffi.C.isprint(c) then
io.write((" '%c'\n"):format(c))
else
io.write((" (%02X)\n"):format(c))
end
elseif tag == TSet then
printcharset(valuetable[tree[index].val]);
io.write("\n")
elseif tag == TOpenCall or tag == TCall then
io.write((" key: %s\n"):format(tostring(valuetable[tree[index].val])))
elseif tag == TBehind then
io.write((" %d\n"):format(tree[index].val))
printtree(tree, ident + 2, index + 1, valuetable);
elseif tag == TCapture then
io.write((" cap: %s n: %s\n"):format(modes[bit.band(tree[index].cap, 0xffff)], valuetable[tree[index].val]))
printtree(tree, ident + 2, index + 1, valuetable);
elseif tag == TRule then
local extra = bit.band(tree[index].cap, RuleLR) == RuleLR and ' left recursive' or ''
extra = extra .. (bit.band(tree[index].cap, Ruleused) ~= Ruleused and ' not used' or '')
io.write((" n: %d key: %s%s\n"):format(bit.band(tree[index].cap, 0xffff) - 1, valuetable[tree[index].val], extra))
printtree(tree, ident + 2, index + 1, valuetable);
-- do not print next rule as a sibling
elseif tag == TGrammar then
local ruleindex = index + 1
io.write((" %d\n"):format(tree[index].val)) -- number of rules
for i = 1, tree[index].val do
printtree(tree, ident + 2, ruleindex, valuetable);
ruleindex = ruleindex + tree[ruleindex].ps
end
assert(tree[ruleindex].tag == TTrue); -- sentinel
else
local sibs = numsiblings[tree[index].tag] or 0
io.write("\n")
if sibs >= 1 then
printtree(tree, ident + 2, index + 1, valuetable);
if sibs >= 2 then
printtree(tree, ident + 2, index + tree[index].ps, valuetable)
end
end
end
end
-- }====================================================== */
return {
printtree = printtree,
printpatt = printpatt,
printcaplist = printcaplist,
printinst = printinst
}

1041
tools/lpeg/lpvm.lua Normal file

File diff suppressed because it is too large Load Diff

286
tools/lpeg/re.lua Normal file
View File

@ -0,0 +1,286 @@
-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $
-- 2014/08/15 changes rostislav
-- imported functions and modules
local tonumber, print, error = tonumber, print, error
local setmetatable = setmetatable
local m = require"lpeglj"
-- 'm' will be used to parse expressions, and 'mm' will be used to
-- create expressions; that is, 're' runs on 'm', creating patterns
-- on 'mm'
local mm = m
-- pattern's metatable
local mt = getmetatable(mm.P(0))
mt = m.version() == "1.0.0.0LJ" and m or mt
-- No more global accesses after this point
local version = _VERSION
if version == "Lua 5.2" then _ENV = nil end
local any = m.P(1)
-- Pre-defined names
local Predef = { nl = m.P"\n" }
local mem
local fmem
local gmem
local function updatelocale ()
mm.locale(Predef)
Predef.a = Predef.alpha
Predef.c = Predef.cntrl
Predef.d = Predef.digit
Predef.g = Predef.graph
Predef.l = Predef.lower
Predef.p = Predef.punct
Predef.s = Predef.space
Predef.u = Predef.upper
Predef.w = Predef.alnum
Predef.x = Predef.xdigit
Predef.A = any - Predef.a
Predef.C = any - Predef.c
Predef.D = any - Predef.d
Predef.G = any - Predef.g
Predef.L = any - Predef.l
Predef.P = any - Predef.p
Predef.S = any - Predef.s
Predef.U = any - Predef.u
Predef.W = any - Predef.w
Predef.X = any - Predef.x
mem = {} -- restart memoization
fmem = {}
gmem = {}
local mt = {__mode = "v"}
setmetatable(mem, mt)
setmetatable(fmem, mt)
setmetatable(gmem, mt)
end
updatelocale()
local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end)
local function getdef (id, defs)
local c = defs and defs[id]
if not c then error("undefined name: " .. id) end
return c
end
local function patt_error (s, i)
local msg = (#s < i + 20) and s:sub(i)
or s:sub(i,i+20) .. "..."
msg = ("pattern error near '%s'"):format(msg)
error(msg, 2)
end
local function mult (p, n)
local np = mm.P(true)
while n >= 1 do
if n%2 >= 1 then np = np * p end
p = p * p
n = n/2
end
return np
end
local function equalcap (s, i, c)
if type(c) ~= "string" then return nil end
local e = #c + i
if type(s) == 'function' then -- stream mode
if s(i, e - 1) == c then return e else return nil end
else
if s:sub(i, e - 1) == c then return e else return nil end
end
end
local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0
local arrow = S * "<-"
local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1
name = m.C(name)
-- a defined name only have meaning in a given environment
local Def = name * m.Carg(1)
local num = m.C(m.R"09"^1) * S / tonumber
local String = "'" * m.C((any - "'")^0) * "'" +
'"' * m.C((any - '"')^0) * '"'
local defined = "%" * Def / function (c,Defs)
local cat = Defs and Defs[c] or Predef[c]
if not cat then error ("name '" .. c .. "' undefined") end
return cat
end
local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
local item = defined + Range + m.C(any)
local Class =
"["
* (m.C(m.P"^"^-1)) -- optional complement symbol
* m.Cf(item * (item - "]")^0, mt.__add) /
function (c, p) return c == "^" and any - p or p end
* "]"
local function adddef (t, k, exp)
if t[k] then
error("'"..k.."' already defined as a rule")
else
t[k] = exp
end
return t
end
local function firstdef (n, r) return adddef({n}, n, r) end
local function NT (n, b, p)
if not b then
error("rule '"..n.."' used outside a grammar")
else return mm.V(n, p or 0)
end
end
local exp = m.P{ "Exp",
Exp = S * ( m.V"Grammar"
+ m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) );
Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul)
* (#seq_follow + patt_error);
Prefix = "&" * S * m.V"Prefix" / mt.__len
+ "!" * S * m.V"Prefix" / mt.__unm
+ m.V"Suffix";
Suffix = m.Cf(m.V"Primary" * S *
( ( m.P"+" * m.Cc(1, mt.__pow)
+ m.P"*" * m.Cc(0, mt.__pow)
+ m.P"?" * m.Cc(-1, mt.__pow)
+ "^" * ( m.Cg(num * m.Cc(mult))
+ m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow))
)
+ "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div))
+ m.P"{}" * m.Cc(nil, m.Ct)
+ m.Cg(Def / getdef * m.Cc(mt.__div))
)
+ "=>" * S * m.Cg(Def / getdef * m.Cc(m.Cmt))
) * S
)^0, function (a,b,f) return f(a,b) end );
Primary = "(" * m.V"Exp" * ")"
+ String / mm.P
+ Class
+ defined
+ "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" /
function (n, p) return mm.Cg(p, n) end
+ "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end
+ m.P"{}" / mm.Cp
+ "{~" * m.V"Exp" * "~}" / mm.Cs
+ "{|" * m.V"Exp" * "|}" / mm.Ct
+ "{" * m.V"Exp" * "}" / mm.C
+ m.P"." * m.Cc(any)
+ (name * m.Cb("G") * (S * ":" * S * num)^-1 * -arrow + "<" * name * m.Cb("G") * (S * ":" * S * num)^-1 * ">") / NT;
Definition = name * arrow * m.V"Exp";
Grammar = m.Cg(m.Cc(true), "G") *
m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0,
adddef) / mm.P
}
local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
local function compile (p, defs)
if mm.type(p) == "pattern" then return p end -- already compiled
local cp = pattern:match(p, 1, defs)
if not cp then error("incorrect pattern", 3) end
return cp
end
local function match (s, p, i)
local cp = mem[p]
if not cp then
cp = compile(p)
mem[p] = cp
end
return cp:match(s, i or 1)
end
local function streammatch (p, i)
local cp = mem[p]
if not cp then
cp = compile(p)
mem[p] = cp
end
return cp:streammatch(i or 1)
end
-- Only for testing purpose
local function emulatestreammatch(s, p, i)
local cp = mem[p]
if not cp then
cp = compile(p)
mem[p] = cp
end
return cp:emulatestreammatch(s, i or 1)
end
local function find (s, p, i)
local cp = fmem[p]
if not cp then
cp = compile(p) / 0
cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
fmem[p] = cp
end
local i, e = cp:match(s, i or 1)
if i then return i, e - 1
else return i
end
end
local function gsub (s, p, rep)
local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
gmem[p] = g
local cp = g[rep]
if not cp then
cp = compile(p)
cp = mm.Cs((cp / rep + 1)^0)
g[rep] = cp
end
return cp:match(s)
end
-- exported names
local re = {
compile = compile,
match = match,
streammatch = streammatch,
emulatestreammatch = emulatestreammatch,
find = find,
gsub = gsub,
updatelocale = updatelocale,
}
if version == "Lua 5.1" then _G.re = re end
return re

25
tools/luajson/json.lua Normal file
View File

@ -0,0 +1,25 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
package.path = package.path .. ";lpeg/?.lua"
local decode = require("json.decode")
local encode = require("json.encode")
local util = require("json.util")
local _G = _G
local _ENV = nil
local json = {
_VERSION = "1.3.4",
_DESCRIPTION = "LuaJSON : customizable JSON decoder/encoder",
_COPYRIGHT = "Copyright (c) 2007-2014 Thomas Harning Jr. <harningt@gmail.com>",
decode = decode,
encode = encode,
util = util
}
_G.json = json
return json

View File

@ -0,0 +1,171 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local error = error
local pcall = pcall
local jsonutil = require("json.util")
local merge = jsonutil.merge
local util = require("json.decode.util")
local decode_state = require("json.decode.state")
local setmetatable, getmetatable = setmetatable, getmetatable
local assert = assert
local ipairs, pairs = ipairs, pairs
local string_char = require("string").char
local type = type
local require = require
local _ENV = nil
local modulesToLoad = {
"composite",
"strings",
"number",
"others"
}
local loadedModules = {
}
local json_decode = {}
json_decode.default = {
unicodeWhitespace = true,
initialObject = false,
nothrow = false
}
local modes_defined = { "default", "strict", "simple" }
json_decode.simple = {}
json_decode.strict = {
unicodeWhitespace = true,
initialObject = true,
nothrow = false
}
for _,name in ipairs(modulesToLoad) do
local mod = require("json.decode." .. name)
if mod.mergeOptions then
for _, mode in pairs(modes_defined) do
mod.mergeOptions(json_decode[mode], mode)
end
end
loadedModules[#loadedModules + 1] = mod
end
-- Shift over default into defaultOptions to permit build optimization
local defaultOptions = json_decode.default
json_decode.default = nil
local function generateDecoder(lexer, options)
-- Marker to permit detection of final end
local marker = {}
local parser = lpeg.Ct((options.ignored * lexer)^0 * lpeg.Cc(marker)) * options.ignored * (lpeg.P(-1) + util.unexpected())
local decoder = function(data)
local state = decode_state.create(options)
local parsed = parser:match(data)
assert(parsed, "Invalid JSON data")
local i = 0
while true do
i = i + 1
local item = parsed[i]
if item == marker then break end
if type(item) == 'function' and item ~= jsonutil.undefined and item ~= jsonutil.null then
item(state)
else
state:set_value(item)
end
end
if options.initialObject then
assert(type(state.previous) == 'table', "Initial value not an object or array")
end
-- Make sure stack is empty
assert(state.i == 0, "Unclosed elements present")
return state.previous
end
if options.nothrow then
return function(data)
local status, rv = pcall(decoder, data)
if status then
return rv
else
return nil, rv
end
end
end
return decoder
end
local function buildDecoder(mode)
mode = mode and merge({}, defaultOptions, mode) or defaultOptions
for _, mod in ipairs(loadedModules) do
if mod.mergeOptions then
mod.mergeOptions(mode)
end
end
local ignored = mode.unicodeWhitespace and util.unicode_ignored or util.ascii_ignored
-- Store 'ignored' in the global options table
mode.ignored = ignored
--local grammar = {
-- [1] = mode.initialObject and (ignored * (object_type + array_type)) or value_type
--}
local lexer
for _, mod in ipairs(loadedModules) do
local new_lexer = mod.generateLexer(mode)
lexer = lexer and lexer + new_lexer or new_lexer
end
return generateDecoder(lexer, mode)
end
-- Since 'default' is nil, we cannot take map it
local defaultDecoder = buildDecoder(json_decode.default)
local prebuilt_decoders = {}
for _, mode in pairs(modes_defined) do
if json_decode[mode] ~= nil then
prebuilt_decoders[json_decode[mode]] = buildDecoder(json_decode[mode])
end
end
--[[
Options:
number => number decode options
string => string decode options
array => array decode options
object => object decode options
initialObject => whether or not to require the initial object to be a table/array
allowUndefined => whether or not to allow undefined values
]]
local function getDecoder(mode)
mode = mode == true and json_decode.strict or mode or json_decode.default
local decoder = mode == nil and defaultDecoder or prebuilt_decoders[mode]
if decoder then
return decoder
end
return buildDecoder(mode)
end
local function decode(data, mode)
local decoder = getDecoder(mode)
return decoder(data)
end
local mt = {}
mt.__call = function(self, ...)
return decode(...)
end
json_decode.getDecoder = getDecoder
json_decode.decode = decode
json_decode.util = util
setmetatable(json_decode, mt)
return json_decode

View File

@ -0,0 +1,190 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local pairs = pairs
local type = type
local lpeg = require("lpeg")
local util = require("json.decode.util")
local jsonutil = require("json.util")
local rawset = rawset
local assert = assert
local tostring = tostring
local error = error
local getmetatable = getmetatable
local _ENV = nil
local defaultOptions = {
array = {
trailingComma = true
},
object = {
trailingComma = true,
number = true,
identifier = true,
setObjectKey = rawset
},
calls = {
defs = nil,
-- By default, do not allow undefined calls to be de-serialized as call objects
allowUndefined = false
}
}
local modeOptions = {
default = nil,
strict = {
array = {
trailingComma = false
},
object = {
trailingComma = false,
number = false,
identifier = false
}
}
}
local function BEGIN_ARRAY(state)
state:push()
state:new_array()
end
local function END_ARRAY(state)
state:end_array()
state:pop()
end
local function BEGIN_OBJECT(state)
state:push()
state:new_object()
end
local function END_OBJECT(state)
state:end_object()
state:pop()
end
local function END_CALL(state)
state:end_call()
state:pop()
end
local function SET_KEY(state)
state:set_key()
end
local function NEXT_VALUE(state)
state:put_value()
end
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, true, 'array', defaultOptions, mode and modeOptions[mode])
jsonutil.doOptionMerge(options, true, 'object', defaultOptions, mode and modeOptions[mode])
jsonutil.doOptionMerge(options, true, 'calls', defaultOptions, mode and modeOptions[mode])
end
local isPattern
if lpeg.type then
function isPattern(value)
return lpeg.type(value) == 'pattern'
end
else
local metaAdd = getmetatable(lpeg.P("")).__add
function isPattern(value)
return getmetatable(value).__add == metaAdd
end
end
local function generateSingleCallLexer(name, func)
if type(name) ~= 'string' and not isPattern(name) then
error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern")
end
-- Allow boolean or function to match up w/ encoding permissions
if type(func) ~= 'boolean' and type(func) ~= 'function' then
error("Invalid functionCalls item: " .. name .. " not a function")
end
local function buildCallCapture(name)
return function(state)
if func == false then
error("Function call on '" .. name .. "' not permitted")
end
state:push()
state:new_call(name, func)
end
end
local nameCallCapture
if type(name) == 'string' then
nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name) / buildCallCapture
else
-- Name matcher expected to produce a capture
nameCallCapture = name * "(" / buildCallCapture
end
-- Call func over nameCallCapture and value to permit function receiving name
return nameCallCapture
end
local function generateNamedCallLexers(options)
if not options.calls or not options.calls.defs then
return
end
local callCapture
for name, func in pairs(options.calls.defs) do
local newCapture = generateSingleCallLexer(name, func)
if not callCapture then
callCapture = newCapture
else
callCapture = callCapture + newCapture
end
end
return callCapture
end
local function generateCallLexer(options)
local lexer
local namedCapture = generateNamedCallLexers(options)
if options.calls and options.calls.allowUndefined then
lexer = generateSingleCallLexer(lpeg.C(util.identifier), true)
end
if namedCapture then
lexer = lexer and lexer + namedCapture or namedCapture
end
if lexer then
lexer = lexer + lpeg.P(")") * lpeg.Cc(END_CALL)
end
return lexer
end
local function generateLexer(options)
local ignored = options.ignored
local array_options, object_options = options.array, options.object
local lexer =
lpeg.P("[") * lpeg.Cc(BEGIN_ARRAY)
+ lpeg.P("]") * lpeg.Cc(END_ARRAY)
+ lpeg.P("{") * lpeg.Cc(BEGIN_OBJECT)
+ lpeg.P("}") * lpeg.Cc(END_OBJECT)
+ lpeg.P(":") * lpeg.Cc(SET_KEY)
+ lpeg.P(",") * lpeg.Cc(NEXT_VALUE)
if object_options.identifier then
-- Add identifier match w/ validation check that it is in key
lexer = lexer + lpeg.C(util.identifier) * ignored * lpeg.P(":") * lpeg.Cc(SET_KEY)
end
local callLexers = generateCallLexer(options)
if callLexers then
lexer = lexer + callLexers
end
return lexer
end
local composite = {
mergeOptions = mergeOptions,
generateLexer = generateLexer
}
return composite

View File

@ -0,0 +1,100 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local tonumber = tonumber
local jsonutil = require("json.util")
local merge = jsonutil.merge
local util = require("json.decode.util")
local _ENV = nil
local digit = lpeg.R("09")
local digits = digit^1
-- Illegal octal declaration
local illegal_octal_detect = #(lpeg.P('0') * digits) * util.denied("Octal numbers")
local int = (lpeg.P('-') + 0) * (lpeg.R("19") * digits + illegal_octal_detect + digit)
local frac = lpeg.P('.') * digits
local exp = lpeg.S("Ee") * (lpeg.S("-+") + 0) * digits
local nan = lpeg.S("Nn") * lpeg.S("Aa") * lpeg.S("Nn")
local inf = lpeg.S("Ii") * lpeg.P("nfinity")
local ninf = lpeg.P('-') * lpeg.S("Ii") * lpeg.P("nfinity")
local hex = (lpeg.P("0x") + lpeg.P("0X")) * lpeg.R("09","AF","af")^1
local defaultOptions = {
nan = true,
inf = true,
frac = true,
exp = true,
hex = false
}
local modeOptions = {}
modeOptions.strict = {
nan = false,
inf = false
}
local nan_value = 0/0
local inf_value = 1/0
local ninf_value = -1/0
--[[
Options: configuration options for number rules
nan: match NaN
inf: match Infinity
frac: match fraction portion (.0)
exp: match exponent portion (e1)
DEFAULT: nan, inf, frac, exp
]]
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode])
end
local function generateLexer(options)
options = options.number
local ret = int
if options.frac then
ret = ret * (frac + 0)
else
ret = ret * (#frac * util.denied("Fractions", "number.frac") + 0)
end
if options.exp then
ret = ret * (exp + 0)
else
ret = ret * (#exp * util.denied("Exponents", "number.exp") + 0)
end
if options.hex then
ret = hex + ret
else
ret = #hex * util.denied("Hexadecimal", "number.hex") + ret
end
-- Capture number now
ret = ret / tonumber
if options.nan then
ret = ret + nan / function() return nan_value end
else
ret = ret + #nan * util.denied("NaN", "number.nan")
end
if options.inf then
ret = ret + ninf / function() return ninf_value end + inf / function() return inf_value end
else
ret = ret + (#ninf + #inf) * util.denied("+/-Inf", "number.inf")
end
return ret
end
local number = {
int = int,
mergeOptions = mergeOptions,
generateLexer = generateLexer
}
return number

View File

@ -0,0 +1,62 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local jsonutil = require("json.util")
local merge = jsonutil.merge
local util = require("json.decode.util")
-- Container module for other JavaScript types (bool, null, undefined)
local _ENV = nil
-- For null and undefined, use the util.null value to preserve null-ness
local booleanCapture =
lpeg.P("true") * lpeg.Cc(true)
+ lpeg.P("false") * lpeg.Cc(false)
local nullCapture = lpeg.P("null")
local undefinedCapture = lpeg.P("undefined")
local defaultOptions = {
allowUndefined = true,
null = jsonutil.null,
undefined = jsonutil.undefined
}
local modeOptions = {}
modeOptions.simple = {
null = false, -- Mapped to nil
undefined = false -- Mapped to nil
}
modeOptions.strict = {
allowUndefined = false
}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode])
end
local function generateLexer(options)
-- The 'or nil' clause allows false to map to a nil value since 'nil' cannot be merged
options = options.others
local valueCapture = (
booleanCapture
+ nullCapture * lpeg.Cc(options.null or nil)
)
if options.allowUndefined then
valueCapture = valueCapture + undefinedCapture * lpeg.Cc(options.undefined or nil)
else
valueCapture = valueCapture + #undefinedCapture * util.denied("undefined", "others.allowUndefined")
end
return valueCapture
end
local others = {
mergeOptions = mergeOptions,
generateLexer = generateLexer
}
return others

View File

@ -0,0 +1,189 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local setmetatable = setmetatable
local jsonutil = require("json.util")
local assert = assert
local type = type
local next = next
local unpack = require("table").unpack or unpack
local _ENV = nil
local state_ops = {}
local state_mt = {
__index = state_ops
}
function state_ops.pop(self)
self.previous_set = true
self.previous = self.active
local i = self.i
-- Load in this array into the active item
self.active = self.stack[i]
self.active_state = self.state_stack[i]
self.active_key = self.key_stack[i]
self.stack[i] = nil
self.state_stack[i] = nil
self.key_stack[i] = nil
self.i = i - 1
end
function state_ops.push(self)
local i = self.i + 1
self.i = i
self.stack[i] = self.active
self.state_stack[i] = self.active_state
self.key_stack[i] = self.active_key
end
function state_ops.put_object_value(self, trailing)
local object_options = self.options.object
if trailing and object_options.trailingComma then
if not self.active_key then
return
end
end
assert(self.active_key, "Missing key value")
object_options.setObjectKey(self.active, self.active_key, self:grab_value())
self.active_key = nil
end
function state_ops.put_array_value(self, trailing)
-- Safety check
if trailing and not self.previous_set and self.options.array.trailingComma then
return
end
local new_index = self.active_state + 1
self.active_state = new_index
self.active[new_index] = self:grab_value()
end
function state_ops.put_value(self, trailing)
if self.active_state == 'object' then
self:put_object_value(trailing)
else
self:put_array_value(trailing)
end
end
function state_ops.new_array(self)
local new_array = {}
if jsonutil.InitArray then
new_array = jsonutil.InitArray(new_array) or new_array
end
self.active = new_array
self.active_state = 0
self.active_key = nil
self:unset_value()
end
function state_ops.end_array(self)
if self.previous_set or self.active_state ~= 0 then
-- Not an empty array
self:put_value(true)
end
if self.active_state ~= #self.active then
-- Store the length in
self.active.n = self.active_state
end
end
function state_ops.new_object(self)
local new_object = {}
self.active = new_object
self.active_state = 'object'
self.active_key = nil
self:unset_value()
end
function state_ops.end_object(self)
if self.previous_set or next(self.active) then
-- Not an empty object
self:put_value(true)
end
end
function state_ops.new_call(self, name, func)
-- TODO setup properly
local new_call = {}
new_call.name = name
new_call.func = func
self.active = new_call
self.active_state = 0
self.active_key = nil
self:unset_value()
end
function state_ops.end_call(self)
if self.previous_set or self.active_state ~= 0 then
-- Not an empty array
self:put_value(true)
end
if self.active_state ~= #self.active then
-- Store the length in
self.active.n = self.active_state
end
local func = self.active.func
if func == true then
func = jsonutil.buildCall
end
self.active = func(self.active.name, unpack(self.active, 1, self.active.n or #self.active))
end
function state_ops.unset_value(self)
self.previous_set = false
self.previous = nil
end
function state_ops.grab_value(self)
assert(self.previous_set, "Previous value not set")
self.previous_set = false
return self.previous
end
function state_ops.set_value(self, value)
assert(not self.previous_set, "Value set when one already in slot")
self.previous_set = true
self.previous = value
end
function state_ops.set_key(self)
assert(self.active_state == 'object', "Cannot set key on array")
local value = self:grab_value()
local value_type = type(value)
if self.options.object.number then
assert(value_type == 'string' or value_type == 'number', "As configured, a key must be a number or string")
else
assert(value_type == 'string', "As configured, a key must be a string")
end
self.active_key = value
end
local function create(options)
local ret = {
options = options,
stack = {},
state_stack = {},
key_stack = {},
i = 0,
active = nil,
active_key = nil,
previous = nil,
active_state = nil
}
return setmetatable(ret, state_mt)
end
local state = {
create = create
}
return state

View File

@ -0,0 +1,133 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local jsonutil = require("json.util")
local util = require("json.decode.util")
local merge = jsonutil.merge
local tonumber = tonumber
local string_char = require("string").char
local floor = require("math").floor
local table_concat = require("table").concat
local error = error
local _ENV = nil
local function get_error(item)
local fmt_string = item .. " in string [%q] @ %i:%i"
return lpeg.P(function(data, index)
local line, line_index, bad_char, last_line = util.get_invalid_character_info(data, index)
local err = fmt_string:format(bad_char, line, line_index)
error(err)
end) * 1
end
local bad_unicode = get_error("Illegal unicode escape")
local bad_hex = get_error("Illegal hex escape")
local bad_character = get_error("Illegal character")
local bad_escape = get_error("Illegal escape")
local knownReplacements = {
["'"] = "'",
['"'] = '"',
['\\'] = '\\',
['/'] = '/',
b = '\b',
f = '\f',
n = '\n',
r = '\r',
t = '\t',
v = '\v',
z = '\z'
}
-- according to the table at http://da.wikipedia.org/wiki/UTF-8
local function utf8DecodeUnicode(code1, code2)
code1, code2 = tonumber(code1, 16), tonumber(code2, 16)
if code1 == 0 and code2 < 0x80 then
return string_char(code2)
end
if code1 < 0x08 then
return string_char(
0xC0 + code1 * 4 + floor(code2 / 64),
0x80 + code2 % 64)
end
return string_char(
0xE0 + floor(code1 / 16),
0x80 + (code1 % 16) * 4 + floor(code2 / 64),
0x80 + code2 % 64)
end
local function decodeX(code)
code = tonumber(code, 16)
return string_char(code)
end
local doSimpleSub = lpeg.C(lpeg.S("'\"\\/bfnrtvz")) / knownReplacements
local doUniSub = lpeg.P('u') * (lpeg.C(util.hexpair) * lpeg.C(util.hexpair) + bad_unicode)
local doXSub = lpeg.P('x') * (lpeg.C(util.hexpair) + bad_hex)
local defaultOptions = {
badChars = '',
additionalEscapes = false, -- disallow untranslated escapes
escapeCheck = #lpeg.S('bfnrtv/\\"xu\'z'), -- no check on valid characters
decodeUnicode = utf8DecodeUnicode,
strict_quotes = false
}
local modeOptions = {}
modeOptions.strict = {
badChars = '\b\f\n\r\t\v',
additionalEscapes = false, -- no additional escapes
escapeCheck = #lpeg.S('bfnrtv/\\"u'), --only these chars are allowed to be escaped
strict_quotes = true
}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode])
end
local function buildCaptureString(quote, badChars, escapeMatch)
local captureChar = (1 - lpeg.S("\\" .. badChars .. quote)) + (lpeg.P("\\") / "" * escapeMatch)
-- During error, force end
local captureString = captureChar^0 + (-#lpeg.P(quote) * bad_character + -1)
return lpeg.P(quote) * lpeg.Cs(captureString) * lpeg.P(quote)
end
local function generateLexer(options)
options = options.strings
local quotes = { '"' }
if not options.strict_quotes then
quotes[#quotes + 1] = "'"
end
local escapeMatch = doSimpleSub
escapeMatch = escapeMatch + doXSub / decodeX
escapeMatch = escapeMatch + doUniSub / options.decodeUnicode
if options.escapeCheck then
escapeMatch = options.escapeCheck * escapeMatch + bad_escape
end
if options.additionalEscapes then
escapeMatch = options.additionalEscapes + escapeMatch
end
local captureString
for i = 1, #quotes do
local cap = buildCaptureString(quotes[i], options.badChars, escapeMatch)
if captureString == nil then
captureString = cap
else
captureString = captureString + cap
end
end
return captureString
end
local strings = {
mergeOptions = mergeOptions,
generateLexer = generateLexer
}
return strings

View File

@ -0,0 +1,121 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local select = select
local pairs, ipairs = pairs, ipairs
local tonumber = tonumber
local string_char = require("string").char
local rawset = rawset
local jsonutil = require("json.util")
local error = error
local setmetatable = setmetatable
local table_concat = require("table").concat
local merge = require("json.util").merge
local _ENV = nil
local function get_invalid_character_info(input, index)
local parsed = input:sub(1, index)
local bad_character = input:sub(index, index)
local _, line_number = parsed:gsub('\n',{})
local last_line = parsed:match("\n([^\n]+.)$") or parsed
return line_number, #last_line, bad_character, last_line
end
local function build_report(msg)
local fmt = msg:gsub("%%", "%%%%") .. " @ character: %i %i:%i [%s] line:\n%s"
return lpeg.P(function(data, pos)
local line, line_index, bad_char, last_line = get_invalid_character_info(data, pos)
local text = fmt:format(pos, line, line_index, bad_char, last_line)
error(text)
end) * 1
end
local function unexpected()
local msg = "unexpected character"
return build_report(msg)
end
local function denied(item, option)
local msg
if option then
msg = ("'%s' denied by option set '%s'"):format(item, option)
else
msg = ("'%s' denied"):format(item)
end
return build_report(msg)
end
-- 09, 0A, 0B, 0C, 0D, 20
local ascii_space = lpeg.S("\t\n\v\f\r ")
local unicode_space
do
local chr = string_char
local u_space = ascii_space
-- \u0085 \u00A0
u_space = u_space + lpeg.P(chr(0xC2)) * lpeg.S(chr(0x85) .. chr(0xA0))
-- \u1680 \u180E
u_space = u_space + lpeg.P(chr(0xE1)) * (lpeg.P(chr(0x9A, 0x80)) + chr(0xA0, 0x8E))
-- \u2000 - \u200A, also 200B
local spacing_end = ""
for i = 0x80,0x8b do
spacing_end = spacing_end .. chr(i)
end
-- \u2028 \u2029 \u202F
spacing_end = spacing_end .. chr(0xA8) .. chr(0xA9) .. chr(0xAF)
u_space = u_space + lpeg.P(chr(0xE2, 0x80)) * lpeg.S(spacing_end)
-- \u205F
u_space = u_space + lpeg.P(chr(0xE2, 0x81, 0x9F))
-- \u3000
u_space = u_space + lpeg.P(chr(0xE3, 0x80, 0x80))
-- BOM \uFEFF
u_space = u_space + lpeg.P(chr(0xEF, 0xBB, 0xBF))
unicode_space = u_space
end
local identifier = lpeg.R("AZ","az","__") * lpeg.R("AZ","az", "__", "09") ^0
local hex = lpeg.R("09","AF","af")
local hexpair = hex * hex
local comments = {
cpp = lpeg.P("//") * (1 - lpeg.P("\n"))^0 * lpeg.P("\n"),
c = lpeg.P("/*") * (1 - lpeg.P("*/"))^0 * lpeg.P("*/")
}
local comment = comments.cpp + comments.c
local ascii_ignored = (ascii_space + comment)^0
local unicode_ignored = (unicode_space + comment)^0
-- Parse the lpeg version skipping patch-values
-- LPEG <= 0.7 have no version value... so 0.7 is value
local DecimalLpegVersion = lpeg.version and tonumber(lpeg.version():match("^(%d+%.%d+)")) or 0.7
local function setObjectKeyForceNumber(t, key, value)
key = tonumber(key) or key
return rawset(t, key, value)
end
local util = {
unexpected = unexpected,
denied = denied,
ascii_space = ascii_space,
unicode_space = unicode_space,
identifier = identifier,
hex = hex,
hexpair = hexpair,
comments = comments,
comment = comment,
ascii_ignored = ascii_ignored,
unicode_ignored = unicode_ignored,
DecimalLpegVersion = DecimalLpegVersion,
get_invalid_character_info = get_invalid_character_info,
setObjectKeyForceNumber = setObjectKeyForceNumber
}
return util

View File

@ -0,0 +1,161 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local type = type
local assert, error = assert, error
local getmetatable, setmetatable = getmetatable, setmetatable
local ipairs, pairs = ipairs, pairs
local require = require
local output = require("json.encode.output")
local util = require("json.util")
local util_merge, isCall = util.merge, util.isCall
local _ENV = nil
--[[
List of encoding modules to load.
Loaded in sequence such that earlier encoders get priority when
duplicate type-handlers exist.
]]
local modulesToLoad = {
"strings",
"number",
"calls",
"others",
"array",
"object"
}
-- Modules that have been loaded
local loadedModules = {}
local json_encode = {}
-- Configuration bases for client apps
local modes_defined = { "default", "strict" }
json_encode.default = {}
json_encode.strict = {
initialObject = true -- Require an object at the root
}
-- For each module, load it and its defaults
for _,name in ipairs(modulesToLoad) do
local mod = require("json.encode." .. name)
if mod.mergeOptions then
for _, mode in pairs(modes_defined) do
mod.mergeOptions(json_encode[mode], mode)
end
end
loadedModules[name] = mod
end
-- NOTE: Nested not found, so assume unsupported until use case arises
local function flattenOutput(out, value)
assert(type(value) ~= 'table')
out = out or {}
out[#out + 1] = value
return out
end
-- Prepares the encoding map from the already provided modules and new config
local function prepareEncodeMap(options)
local map = {}
for _, name in ipairs(modulesToLoad) do
local encodermap = loadedModules[name].getEncoder(options[name])
for valueType, encoderSet in pairs(encodermap) do
map[valueType] = flattenOutput(map[valueType], encoderSet)
end
end
return map
end
--[[
Encode a value with a given encoding map and state
]]
local function encodeWithMap(value, map, state, isObjectKey)
local t = type(value)
local encoderList = assert(map[t], "Failed to encode value, unhandled type: " .. t)
for _, encoder in ipairs(encoderList) do
local ret = encoder(value, state, isObjectKey)
if false ~= ret then
return ret
end
end
error("Failed to encode value, encoders for " .. t .. " deny encoding")
end
local function getBaseEncoder(options)
local encoderMap = prepareEncodeMap(options)
if options.preProcess then
local preProcess = options.preProcess
return function(value, state, isObjectKey)
local ret = preProcess(value, isObjectKey or false)
if nil ~= ret then
value = ret
end
return encodeWithMap(value, encoderMap, state)
end
end
return function(value, state, isObjectKey)
return encodeWithMap(value, encoderMap, state)
end
end
--[[
Retreive an initial encoder instance based on provided options
the initial encoder is responsible for initializing state
State has at least these values configured: encode, check_unique, already_encoded
]]
function json_encode.getEncoder(options)
options = options and util_merge({}, json_encode.default, options) or json_encode.default
local encode = getBaseEncoder(options)
local function initialEncode(value)
if options.initialObject then
local errorMessage = "Invalid arguments: expects a JSON Object or Array at the root"
assert(type(value) == 'table' and not isCall(value, options), errorMessage)
end
local alreadyEncoded = {}
local function check_unique(value)
assert(not alreadyEncoded[value], "Recursive encoding of value")
alreadyEncoded[value] = true
end
local outputEncoder = options.output and options.output() or output.getDefault()
local state = {
encode = encode,
check_unique = check_unique,
already_encoded = alreadyEncoded, -- To unmark encoding when moving up stack
outputEncoder = outputEncoder
}
local ret = encode(value, state)
if nil ~= ret then
return outputEncoder.simple and outputEncoder.simple(ret) or ret
end
end
return initialEncode
end
-- CONSTRUCT STATE WITH FOLLOWING (at least)
--[[
encoder
check_unique -- used by inner encoders to make sure value is unique
already_encoded -- used to unmark a value as unique
]]
function json_encode.encode(data, options)
return json_encode.getEncoder(options)(data)
end
local mt = {}
mt.__call = function(self, ...)
return json_encode.encode(...)
end
setmetatable(json_encode, mt)
return json_encode

View File

@ -0,0 +1,110 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local jsonutil = require("json.util")
local type = type
local pairs = pairs
local assert = assert
local table = require("table")
local math = require("math")
local table_concat = table.concat
local math_floor, math_modf = math.floor, math.modf
local jsonutil = require("json.util")
local util_IsArray = jsonutil.IsArray
local _ENV = nil
local defaultOptions = {
isArray = util_IsArray
}
local modeOptions = {}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'array', defaultOptions, mode and modeOptions[mode])
end
--[[
Utility function to determine whether a table is an array or not.
Criteria for it being an array:
* ExternalIsArray returns true (or false directly reports not-array)
* If the table has an 'n' value that is an integer >= 1 then it
is an array... may result in false positives (should check some values
before it)
* It is a contiguous list of values with zero string-based keys
]]
local function isArray(val, options)
local externalIsArray = options and options.isArray
if externalIsArray then
local ret = externalIsArray(val)
if ret == true or ret == false then
return ret
end
end
-- Use the 'n' element if it's a number
if type(val.n) == 'number' and math_floor(val.n) == val.n and val.n >= 1 then
return true
end
local len = #val
for k,v in pairs(val) do
if type(k) ~= 'number' then
return false
end
local _, decim = math_modf(k)
if not (decim == 0 and 1<=k) then
return false
end
if k > len then -- Use Lua's length as absolute determiner
return false
end
end
return true
end
--[[
Cleanup function to unmark a value as in the encoding process and return
trailing results
]]
local function unmarkAfterEncode(tab, state, ...)
state.already_encoded[tab] = nil
return ...
end
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
local function encodeArray(tab, state)
if not isArray(tab, options) then
return false
end
-- Make sure this value hasn't been encoded yet
state.check_unique(tab)
local encode = state.encode
local compositeEncoder = state.outputEncoder.composite
local valueEncoder = [[
for i = 1, (composite.n or #composite) do
local val = composite[i]
PUTINNER(i ~= 1)
val = encode(val, state)
val = val or ''
if val then
PUTVALUE(val)
end
end
]]
return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '[', ']', ',', tab, encode, state))
end
return { table = encodeArray }
end
local array = {
mergeOptions = mergeOptions,
isArray = isArray,
getEncoder = getEncoder
}
return array

View File

@ -0,0 +1,68 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local table = require("table")
local table_concat = table.concat
local select = select
local getmetatable, setmetatable = getmetatable, setmetatable
local assert = assert
local jsonutil = require("json.util")
local isCall, decodeCall = jsonutil.isCall, jsonutil.decodeCall
local _ENV = nil
local defaultOptions = {
}
-- No real default-option handling needed...
local modeOptions = {}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'calls', defaultOptions, mode and modeOptions[mode])
end
--[[
Encodes 'value' as a function call
Must have parameters in the 'callData' field of the metatable
name == name of the function call
parameters == array of parameters to encode
]]
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
local function encodeCall(value, state)
if not isCall(value) then
return false
end
local encode = state.encode
local name, params = decodeCall(value)
local compositeEncoder = state.outputEncoder.composite
local valueEncoder = [[
for i = 1, (composite.n or #composite) do
local val = composite[i]
PUTINNER(i ~= 1)
val = encode(val, state)
val = val or ''
if val then
PUTVALUE(val)
end
end
]]
return compositeEncoder(valueEncoder, name .. '(', ')', ',', params, encode, state)
end
return {
table = encodeCall,
['function'] = encodeCall
}
end
local calls = {
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return calls

View File

@ -0,0 +1,58 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local tostring = tostring
local assert = assert
local jsonutil = require("json.util")
local huge = require("math").huge
local _ENV = nil
local defaultOptions = {
nan = true,
inf = true
}
local modeOptions = {}
modeOptions.strict = {
nan = false,
inf = false
}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode])
end
local function encodeNumber(number, options)
if number ~= number then
assert(options.nan, "Invalid number: NaN not enabled")
return "NaN"
end
if number == huge then
assert(options.inf, "Invalid number: Infinity not enabled")
return "Infinity"
end
if number == -huge then
assert(options.inf, "Invalid number: Infinity not enabled")
return "-Infinity"
end
return tostring(number)
end
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
return {
number = function(number, state)
return encodeNumber(number, options)
end
}
end
local number = {
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return number

View File

@ -0,0 +1,77 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local pairs = pairs
local assert = assert
local type = type
local tostring = tostring
local table_concat = require("table").concat
local jsonutil = require("json.util")
local _ENV = nil
local defaultOptions = {
}
local modeOptions = {}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'object', defaultOptions, mode and modeOptions[mode])
end
--[[
Cleanup function to unmark a value as in the encoding process and return
trailing results
]]
local function unmarkAfterEncode(tab, state, ...)
state.already_encoded[tab] = nil
return ...
end
--[[
Encode a table as a JSON Object ( keys = strings, values = anything else )
]]
local function encodeTable(tab, options, state)
-- Make sure this value hasn't been encoded yet
state.check_unique(tab)
local encode = state.encode
local compositeEncoder = state.outputEncoder.composite
local valueEncoder = [[
local first = true
for k, v in pairs(composite) do
local ti = type(k)
assert(ti == 'string' or ti == 'number' or ti == 'boolean', "Invalid object index type: " .. ti)
local name = encode(tostring(k), state, true)
if first then
first = false
else
name = ',' .. name
end
PUTVALUE(name .. ':')
local val = encode(v, state)
val = val or ''
if val then
PUTVALUE(val)
end
end
]]
return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '{', '}', nil, tab, encode, state))
end
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
return {
table = function(tab, state)
return encodeTable(tab, options, state)
end
}
end
local object = {
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return object

View File

@ -0,0 +1,66 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local tostring = tostring
local assert = assert
local jsonutil = require("json.util")
local type = type
local _ENV = nil
-- Shortcut that works
local encodeBoolean = tostring
local defaultOptions = {
allowUndefined = true,
null = jsonutil.null,
undefined = jsonutil.undefined
}
local modeOptions = {}
modeOptions.strict = {
allowUndefined = false
}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode])
end
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
local function encodeOthers(value, state)
if value == options.null then
return 'null'
elseif value == options.undefined then
assert(options.allowUndefined, "Invalid value: Unsupported 'Undefined' parameter")
return 'undefined'
else
return false
end
end
local function encodeBoolean(value, state)
return value and 'true' or 'false'
end
local nullType = type(options.null)
local undefinedType = options.undefined and type(options.undefined)
-- Make sure that all of the types handled here are handled
local ret = {
boolean = encodeBoolean,
['nil'] = function() return 'null' end,
[nullType] = encodeOthers
}
if undefinedType then
ret[undefinedType] = encodeOthers
end
return ret
end
local others = {
encodeBoolean = encodeBoolean,
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return others

View File

@ -0,0 +1,91 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local type = type
local assert, error = assert, error
local table_concat = require("table").concat
local loadstring = loadstring or load
local io = require("io")
local setmetatable = setmetatable
local output_utility = require("json.encode.output_utility")
local _ENV = nil
local tableCompositeCache = setmetatable({}, {__mode = 'v'})
local TABLE_VALUE_WRITER = [[
ret[#ret + 1] = %VALUE%
]]
local TABLE_INNER_WRITER = ""
--[[
nextValues can output a max of two values to throw into the data stream
expected to be called until nil is first return value
value separator should either be attached to v1 or in innerValue
]]
local function defaultTableCompositeWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state)
if type(nextValues) == 'string' then
local fun = output_utility.prepareEncoder(defaultTableCompositeWriter, nextValues, innerValue, TABLE_VALUE_WRITER, TABLE_INNER_WRITER)
local ret = {}
fun(composite, ret, encode, state)
return beginValue .. table_concat(ret, innerValue) .. closeValue
end
end
-- no 'simple' as default action is just to return the value
local function getDefault()
return { composite = defaultTableCompositeWriter }
end
-- BEGIN IO-WRITER OUTPUT
local IO_INNER_WRITER = [[
if %WRITE_INNER% then
state.__outputFile:write(%INNER_VALUE%)
end
]]
local IO_VALUE_WRITER = [[
state.__outputFile:write(%VALUE%)
]]
local function buildIoWriter(output)
if not output then -- Default to stdout
output = io.output()
end
local function ioWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state)
-- HOOK OUTPUT STATE
state.__outputFile = output
if type(nextValues) == 'string' then
local fun = output_utility.prepareEncoder(ioWriter, nextValues, innerValue, IO_VALUE_WRITER, IO_INNER_WRITER)
local ret = {}
output:write(beginValue)
fun(composite, ret, encode, state)
output:write(closeValue)
return nil
end
end
local function ioSimpleWriter(encoded)
if encoded then
output:write(encoded)
end
return nil
end
return { composite = ioWriter, simple = ioSimpleWriter }
end
local function getIoWriter(output)
return function()
return buildIoWriter(output)
end
end
local output = {
getDefault = getDefault,
getIoWriter = getIoWriter
}
return output

View File

@ -0,0 +1,54 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local setmetatable = setmetatable
local assert, loadstring = assert, loadstring or load
local _ENV = nil
-- Key == weak, if main key goes away, then cache cleared
local outputCache = setmetatable({}, {__mode = 'k'})
-- TODO: inner tables weak?
local function buildFunction(nextValues, innerValue, valueWriter, innerWriter)
local putInner = ""
if innerValue and innerWriter then
-- Prepare the lua-string representation of the separator to put in between values
local formattedInnerValue = ("%q"):format(innerValue)
-- Fill in the condition %WRITE_INNER% and the %INNER_VALUE% to actually write
putInner = innerWriter:gsub("%%WRITE_INNER%%", "%%1"):gsub("%%INNER_VALUE%%", formattedInnerValue)
end
-- Template-in the value writer (if present) and its conditional argument
local functionCode = nextValues:gsub("PUTINNER(%b())", putInner)
-- %VALUE% is to be filled in by the value-to-write
valueWriter = valueWriter:gsub("%%VALUE%%", "%%1")
-- Template-in the value writer with its argument
functionCode = functionCode:gsub("PUTVALUE(%b())", valueWriter)
functionCode = [[
return function(composite, ret, encode, state)
]] .. functionCode .. [[
end
]]
return assert(loadstring(functionCode))()
end
local function prepareEncoder(cacheKey, nextValues, innerValue, valueWriter, innerWriter)
local cache = outputCache[cacheKey]
if not cache then
cache = {}
outputCache[cacheKey] = cache
end
local fun = cache[nextValues]
if not fun then
fun = buildFunction(nextValues, innerValue, valueWriter, innerWriter)
cache[nextValues] = fun
end
return fun
end
local output_utility = {
prepareEncoder = prepareEncoder
}
return output_utility

View File

@ -0,0 +1,88 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local string_char = require("string").char
local pairs = pairs
local jsonutil = require("json.util")
local util_merge = jsonutil.merge
local _ENV = nil
local normalEncodingMap = {
['"'] = '\\"',
['\\'] = '\\\\',
['/'] = '\\/',
['\b'] = '\\b',
['\f'] = '\\f',
['\n'] = '\\n',
['\r'] = '\\r',
['\t'] = '\\t',
['\v'] = '\\v' -- not in official spec, on report, removing
}
local xEncodingMap = {}
for char, encoded in pairs(normalEncodingMap) do
xEncodingMap[char] = encoded
end
-- Pre-encode the control characters to speed up encoding...
-- NOTE: UTF-8 may not work out right w/ JavaScript
-- JavaScript uses 2 bytes after a \u... yet UTF-8 is a
-- byte-stream encoding, not pairs of bytes (it does encode
-- some letters > 1 byte, but base case is 1)
for i = 0, 255 do
local c = string_char(i)
if c:match('[%z\1-\031\128-\255]') and not normalEncodingMap[c] then
-- WARN: UTF8 specializes values >= 0x80 as parts of sequences...
-- without \x encoding, do not allow encoding > 7F
normalEncodingMap[c] = ('\\u%.4X'):format(i)
xEncodingMap[c] = ('\\x%.2X'):format(i)
end
end
local defaultOptions = {
xEncode = false, -- Encode single-bytes as \xXX
processor = nil, -- Simple processor for the string prior to quoting
-- / is not required to be quoted but it helps with certain decoding
-- Required encoded characters, " \, and 00-1F (0 - 31)
encodeSet = '\\"/%z\1-\031',
encodeSetAppend = nil -- Chars to append to the default set
}
local modeOptions = {}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode])
end
local function getEncoder(options)
options = options and util_merge({}, defaultOptions, options) or defaultOptions
local encodeSet = options.encodeSet
if options.encodeSetAppend then
encodeSet = encodeSet .. options.encodeSetAppend
end
local encodingMap = options.xEncode and xEncodingMap or normalEncodingMap
local encodeString
if options.processor then
local processor = options.processor
encodeString = function(s, state)
return '"' .. processor(s:gsub('[' .. encodeSet .. ']', encodingMap)) .. '"'
end
else
encodeString = function(s, state)
return '"' .. s:gsub('[' .. encodeSet .. ']', encodingMap) .. '"'
end
end
return {
string = encodeString
}
end
local strings = {
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return strings

152
tools/luajson/json/util.lua Normal file
View File

@ -0,0 +1,152 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local type = type
local print = print
local tostring = tostring
local pairs = pairs
local getmetatable, setmetatable = getmetatable, setmetatable
local select = select
local _ENV = nil
local function foreach(tab, func)
for k, v in pairs(tab) do
func(k,v)
end
end
local function printValue(tab, name)
local parsed = {}
local function doPrint(key, value, space)
space = space or ''
if type(value) == 'table' then
if parsed[value] then
print(space .. key .. '= <' .. parsed[value] .. '>')
else
parsed[value] = key
print(space .. key .. '= {')
space = space .. ' '
foreach(value, function(key, value) doPrint(key, value, space) end)
end
else
if type(value) == 'string' then
value = '[[' .. tostring(value) .. ']]'
end
print(space .. key .. '=' .. tostring(value))
end
end
doPrint(name, tab)
end
local function clone(t)
local ret = {}
for k,v in pairs(t) do
ret[k] = v
end
return ret
end
local function inner_merge(t, remaining, from, ...)
if remaining == 0 then
return t
end
if from then
for k,v in pairs(from) do
t[k] = v
end
end
return inner_merge(t, remaining - 1, ...)
end
--[[*
Shallow-merges tables in order onto the first table.
@param t table to merge entries onto
@param ... sequence of 0 or more tables to merge onto 't'
@returns table 't' from input
]]
local function merge(t, ...)
return inner_merge(t, select('#', ...), ...)
end
-- Function to insert nulls into the JSON stream
local function null()
return null
end
-- Marker for 'undefined' values
local function undefined()
return undefined
end
local ArrayMT = {}
--[[
Return's true if the metatable marks it as an array..
Or false if it has no array component at all
Otherwise nil to get the normal detection component working
]]
local function IsArray(value)
if type(value) ~= 'table' then return false end
local meta = getmetatable(value)
local ret = meta == ArrayMT or (meta ~= nil and meta.__is_luajson_array)
if not ret then
if #value == 0 then return false end
else
return ret
end
end
local function InitArray(array)
setmetatable(array, ArrayMT)
return array
end
local CallMT = {}
local function isCall(value)
return CallMT == getmetatable(value)
end
local function buildCall(name, ...)
local callData = {
name = name,
parameters = {n = select('#', ...), ...}
}
return setmetatable(callData, CallMT)
end
local function decodeCall(callData)
if not isCall(callData) then return nil end
return callData.name, callData.parameters
end
local function doOptionMerge(options, nested, name, defaultOptions, modeOptions)
if nested then
modeOptions = modeOptions and modeOptions[name]
defaultOptions = defaultOptions and defaultOptions[name]
end
options[name] = merge(
{},
defaultOptions,
modeOptions,
options[name]
)
end
local json_util = {
printValue = printValue,
clone = clone,
merge = merge,
null = null,
undefined = undefined,
IsArray = IsArray,
InitArray = InitArray,
isCall = isCall,
buildCall = buildCall,
decodeCall = decodeCall,
doOptionMerge = doOptionMerge
}
return json_util

View File

@ -0,0 +1,95 @@
package.path = package.path .. ";lua_scripts/libraries/luajson/?.lua"
local JSON = require"json"
local jsontest = [[{ 1:{"scn_ptz_id":"",
"scn_ptz_prepos":"Preset 176",
"scn_ptz_order":1,
"scn_ptz_duration":"30",
"scn_ptz_rally_delay":"2"}
,
2:{"scn_ptz_id":"","scn_ptz_prepos":"route","scn_ptz_order":2,"scn_ptz_duration":"30","scn_ptz_rally_delay":"2"} }
]]
local jsontest2 = [[{
"extension":"mpg",
"id":1545148451781,
"name":"Foule_1280x720p.mpg",
"size":67240746,
"date":1545148451,
"mime":"video\/mpeg",
"filename":"1545148451781.mpg",
"dir":"\/home\/pixalarm_data\/fileprocessor_data",
"function_metadatas":
{
"function_faceblur":
{
"date":1545228627,
"current_frame":"845",
"polygons":[
{
"polygon_id":"new_1",
"polygon_vertex":"[
[0.14254859611231102,0.12476007677543186],[0.13174946004319654,0.4740882917466411],
[0.3898488120950324,0.6621880998080614],[0.4038876889848812,0.11516314779270634]
]",
"polygon_frame_start":"1",
"polygon_frame_stop":"300",
"polygon_type":"full_blur"
},
{
"polygon_id":"new_2",
"polygon_vertex":"[
[0.6198704103671706,0.1727447216890595],[0.5496760259179265,0.6007677543186181],
[0.7775377969762419,0.7946257197696737],[0.9028077753779697,0.761996161228407],
[0.9481641468682506,0.2821497120921305],[0.7829373650107991,0.04798464491362764]
]",
"polygon_frame_start":"200",
"polygon_frame_stop":"845",
"polygon_type":"no_blur"
}
],
"framecuts":[
["17","110"],
["248","298"],
["488","620"],
["378","428"]
],
"face_selection":[
{
"frame":"21",
"x":"0.5",
"y":"0.356"
},
{
"frame":"108",
"x":"0.4289",
"y":"0.275"
},
{
"frame":"294",
"x":"0.726",
"y":"0.2364"
}
],
"blur_type":"blur",
"blur_area":"face"
}
},
"total_frame":"845",
"status":"DECODE_FINISHED",
"fps":"25.00"
}]]
local res = JSON.decode(jsontest2)
for k, v in pairs(res) do
print( k, v)
end
res = JSON.decode( '{"content" : {},"date" : "2014-12-30T08:29:48Z","error" : {"code" : 0,"httpcode" : 200,"message" : ""},"status" : 1}' )
for k, v in pairs(res) do
print( k, v)
end
local jsondata = JSON.encode( res )
print(jsondata)