From c5cd7d87001ed5738c428dadb38f73999c033a87 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 18 Mar 2026 02:13:36 -0700 Subject: [PATCH 01/60] [ShortcutsSettings] Fix duplicate aPlay shortcut; change play shortcut's group (#6716) * [ShortcutsSettings] Fix duplicate aPlay shortcut; change play shortcut's group * update description --- cockatrice/src/client/settings/shortcuts_settings.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 47332b8c4..51615745b 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -513,6 +513,9 @@ private: {"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card"), parseSequenceString(""), ShortcutGroup::Playing_Area)}, + {"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card, Face Down"), + parseSequenceString(""), + ShortcutGroup::Playing_Area)}, {"Player/aAttach", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Attach Card..."), parseSequenceString("Ctrl+Alt+A"), ShortcutGroup::Playing_Area)}, @@ -560,12 +563,6 @@ private: {"Player/aMoveToTopLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"), parseSequenceString(""), ShortcutGroup::Move_selected)}, - {"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield, Face Down"), - parseSequenceString(""), - ShortcutGroup::Move_selected)}, - {"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"), - parseSequenceString(""), - ShortcutGroup::Move_selected)}, {"Player/aViewHand", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)}, {"Player/aViewGraveyard", From 38c85e6db16dbc692dbc908c1a9693a11761788c Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:24:34 -0700 Subject: [PATCH 02/60] [Dialog] Reduce spacing in create local game dialog (#6719) --- .../src/interface/widgets/dialogs/dlg_local_game_options.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp index 52466ff10..a9adfd907 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp @@ -20,6 +20,7 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) numberPlayersLabel->setBuddy(numberPlayersEdit); auto *generalGrid = new QGridLayout; + generalGrid->setContentsMargins(5, 5, 5, 5); generalGrid->addWidget(numberPlayersLabel, 0, 0); generalGrid->addWidget(numberPlayersEdit, 0, 1); generalGroupBox = new QGroupBox(tr("General"), this); @@ -33,6 +34,7 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); auto *gameSetupGrid = new QGridLayout; + gameSetupGrid->setContentsMargins(5, 5, 5, 5); gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0); gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1); gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this); From 067fe9b5349a673f2d61f0cf5e2e92e0873013b4 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:13:43 +0100 Subject: [PATCH 03/60] Translate cockatrice/cockatrice_en@source.ts in it (#6724) 100% translated source file: 'cockatrice/cockatrice_en@source.ts' on 'it'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- cockatrice/translations/cockatrice_it.ts | 348 ++++++++++++----------- 1 file changed, 180 insertions(+), 168 deletions(-) diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index a0765fbfb..5893d4121 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -101,7 +101,7 @@ Controlla se la cartella è valida e prova ancora. Add Analytics Panel - + Aggiungi pannello di analisi @@ -156,7 +156,13 @@ You will not be able to manage printing preferences on a per-deck basis, or see You will have to use the Set Manager, available through Card Database -> Manage Sets. Are you sure you would like to enable this feature? - + Abilitare questa funzione disattiverà il Selettore di stampa. + +Non potrai gestire le preferenze delle stampe per i singoli mazzi, o vedere le stampe scelte dagli altri giocatori per i loro mazzi. + +Dovrai usare il Gestore dei set, raggiungibile tramite Database carte -> Organizza set. + +Sicuro di voler abilitare questa funzione? @@ -167,12 +173,18 @@ You can now choose printings on a per-deck basis in the Deck Editor and configur You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). Are you sure you would like to disable this feature? - + Disabilitare questa funzione attiverà il Selettore di stampa. + +Potrai gestire le preferenze delle stampe per i singoli mazzi nell'Editor, e configurare quale stampa viene aggiunta di default a un mazzo fissandola nel selettore. + +Potrai anche usare il Gestore dei set per personalizzare l'ordine delle stampe nel Selettore (sono disponibili anche ordinamenti predefiniti come alfabetico o per data di uscita). + +Sicuro di voler disabilitare questa funzione? Confirm Change - + Conferma modifica @@ -217,7 +229,7 @@ Are you sure you would like to disable this feature? Show game filter toolbar above list in room tab - + Mostra barra di filtro sopra la lista delle partite @@ -237,7 +249,7 @@ Are you sure you would like to disable this feature? Override all card art with personal set preference (Pre-ProviderID change behavior) - + Determina l'illustrazione della carta attraverso la priorità dei set (come prima dell'implementazione del ProviderID) @@ -326,7 +338,7 @@ Are you sure you would like to disable this feature? Open Deck in Deck Editor - + Apri mazzo nell'editor dei mazzi @@ -550,7 +562,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Name (Exact) - + Nome (esatto) @@ -674,12 +686,12 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Set: - + Set: Collector Number: - + Numero di collezione: @@ -822,7 +834,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Card Size - Dimensioni della carta + Dimensioni carte @@ -995,22 +1007,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Add Panel - + Aggiungi pannello Remove Panel - + Rimuovi pannello Save Layout - + Salva disposizione Load Layout - + Carica disposizione @@ -1018,7 +1030,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Card Database - + Database carte @@ -1082,7 +1094,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Loading Database... - + Caricamento database... @@ -1147,7 +1159,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Format: - + Formato: @@ -1488,32 +1500,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Undo - + Annulla Redo - + Ripeti Undo/Redo history - + Annulla/Ripeti Storico Click on an entry to revert to that point in the history. - + Clicca su una riga per ritornare a quel punto nello storico delle modifiche. [redo] - + [ripeti] [undo] - + [annulla] @@ -1746,57 +1758,57 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Rename deck to "%1" from "%2" - + Rinominato mazzo in "%1" da "%2" Updated comments (was %1 chars, now %2 chars) - + Commenti aggiornati (prima %1 caratteri, ora %2 caratteri) Set banner card to %1 (%2) - + Impostata carta copertina a %1 (%2) Tags changed - + Etichette modificate Set format to %1 - + Formato impostato a %1 Added (%1): %2 (%3) %4 - + Aggiunto (%1): %2 (%3) %4 Moved to %1 1 × "%2" (%3) - + Spostato in %1 1 × "%2" (%3) Removed "%1" (all copies) - + Rimosso "%1" (tutte le copie) %1 1 × "%2" (%3) - + %1 1 × "%2" (%3) Added - + Aggiunto Removed - + Rimosso @@ -2150,7 +2162,7 @@ Vuoi convertire il mazzo al formato .cod? Create game as judge - + Crea partita come arbitro @@ -2526,7 +2538,7 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Il nome scelto è in conflitto con una carta o pedina esistente. -Assicurati di abilitare il set "Pedine" nella finestra "Organizza set" per visualizzarle correttamente. +Assicurati di abilitare il set "Pedine" nella finestra "Gestisci Espansioni" per visualizzarle correttamente. @@ -2618,7 +2630,7 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza Hide games not created by buddies Hide games not created by buddy - + Nascondi partite non create dagli amici @@ -3150,17 +3162,17 @@ La tua email verrà utilizzata per verificare il tuo account. Bulk modified printings. - + Stampe modificate massivamente. Cleared all printing information. - + Informazioni sulle stampe eliminate. Set all printings to preferred. - + Imposta tutte le stampe come preferite. @@ -3556,63 +3568,63 @@ Dovrai scaricare la nuova versione manualmente. Draw Probability Settings - + Impostazioni probabilità di pescare Criteria: - + Criterio: Card Name - + Nome carta Type - + Tipo Subtype - + Sottotipo Mana Value - + Valore di mana Exactness: - + Precisione: At least - + Almeno Exactly - + Esattamente Quantity (N): - + Quantità (N): Cards drawn (M): - + Carte pescate (M): cards - + carte @@ -3620,67 +3632,67 @@ Dovrai scaricare la nuova versione manualmente. Draw Probability - + Probabilità di pescare Probability of drawing - + Probabilità di pescare Card Name - + Nome carta Type - + Tipo Subtype - + Sottotipo Mana Value - + Valore di mana At least - + Almeno Exactly - + Esattamente card(s) having drawn at least - + carta/e avendo pescato almeno cards - + carte Category - + Categoria Qty - + Quantità Odds (%) - + Probabilità (%) @@ -3757,7 +3769,7 @@ Dovrai scaricare la nuova versione manualmente. Game Changers - + Carte decisive @@ -3765,7 +3777,7 @@ Dovrai scaricare la nuova versione manualmente. Budget - + Budget @@ -3916,12 +3928,12 @@ Dovrai scaricare la nuova versione manualmente. Join Game as Judge - + Unisciti a una partita come arbitro Spectate Game as Judge - + Osserva partita come arbitro @@ -3961,7 +3973,7 @@ Dovrai scaricare la nuova versione manualmente. Join as judge - + Unisciti come arbitro @@ -3971,7 +3983,7 @@ Dovrai scaricare la nuova versione manualmente. Join as judge spectator - + Unisciti come arbitro spettatore @@ -3989,32 +4001,32 @@ Dovrai scaricare la nuova versione manualmente. All types - + Tutti i tipi Filter by game name... - + Filtra per nome della partita... Filter by game type/format - + Filtra per tipo / formato di partita Hide games not created by buddies - + Nascondi le partite non create da amici Hide full games - + Nascondi partite al completo Hide started games - + Nascondi partite iniziate @@ -4297,7 +4309,7 @@ Dovrai scaricare la nuova versione manualmente. &All players - + &Tutti i giocatori @@ -4330,37 +4342,37 @@ Dovrai scaricare la nuova versione manualmente. Sort hand by... - + Ordina mano per... Name - + Nome Type - + Tipo Mana Value - + Valore di mana Take &mulligan (Choose hand size) - + Effettua un &mulligan (Scegli numero di carte) Take mulligan (Same hand size) - + Effettua un mulligan (Stessa dimensione della mano) Take mulligan (Hand size - 1) - + Effettua un mulligan (Dimensione della mano - 1) @@ -4396,7 +4408,7 @@ Dovrai scaricare la nuova versione manualmente. All players - + Tutti i giocatori @@ -4429,7 +4441,7 @@ Dovrai scaricare la nuova versione manualmente. Browse Archidekt - + Esplora Archidekt @@ -4638,17 +4650,17 @@ Dovrai scaricare la nuova versione manualmente. &All players - + &Tutti i giocatori Reveal top cards of library - + Rivela le carte in cima al grimorio Number of cards: (max. %1) - + Numero di carte: (max. %1) @@ -5186,7 +5198,7 @@ La tua versione è la %1, la versione online è la %2. &Manage sets... - &Organizza set... + &Gestisci Espansioni... @@ -5358,7 +5370,7 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. Ciao! Sembra che tu stia usando questa versione di Cockatrice per la prima volta Tutti i set nell'archivio delle carte sono stati abilitati. -Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti nella finestra "Organizza set". +Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti nella finestra "Gestisci Espansioni". @@ -5488,42 +5500,42 @@ Il database delle carte verrà ricaricato. Mana Base Configuration - + Configurazione Base di mana Display type: - + Modalità di visualizzazione: pie - + a torta bar - + a barre combinedBar - + a barre combinate Filter Colors (optional): - + Filtra per colori (opzionale): OK - + OK Cancel - + Annulla @@ -5539,47 +5551,47 @@ Il database delle carte verrà ricaricato. Group By: - + Raggruppa per: type - + tipo color - + colore subtype - + sottotipo power - + forza toughness - + costituzione Filters (optional): - + Filtri (opzionali): Show main bar row - + Mostra il grafico principale Show per-category rows - + Mostra i grafici per categorie @@ -5595,27 +5607,27 @@ Il database delle carte verrà ricaricato. Display type: - + Modalità di visualizzazione: pie - + A torta bar - + A barre combinedBar - + A barra combinata Filter Colors (optional): - + Filtri per colore (opzionali): @@ -5631,27 +5643,27 @@ Il database delle carte verrà ricaricato. Top display type: - + Modalità di visualizzazione principale: pie - + A torta bar - + A barre Colors: - + Colori: Show per-color rows - + Mostra grafici per colore @@ -5659,12 +5671,12 @@ Il database delle carte verrà ricaricato. %1 pips (%2 cards) - + %1 simboli (%2 carte) %1 mana (%2 cards) - + %1 mana (%2 carte) @@ -5672,12 +5684,12 @@ Il database delle carte verrà ricaricato. Mana Production + Devotion - + Produzione + Devozione di mana Mana Distribution Settings - + Impostazioni distribuzione di mana @@ -7637,58 +7649,58 @@ Controlla le impostazioni! Desc. - + Decrescente Asc. - + Crescente Any Bracket - + Qualsiasi fascia Deck name contains... - + Nome del mazzo contiene... Owner name contains... - + Nome del proprietario contiene... Disabled - + Disabilitato Search - + Cerca Formats - + Formati Min. # of Cards: - + Num. minimo di carte: Page: - + Pagina: Archidekt: - + Archidekt: @@ -7716,7 +7728,7 @@ Controlla le impostazioni! Card Database - + Database carte @@ -7819,12 +7831,12 @@ Controlla le impostazioni! Visual Deck View - Galleria mazzo + Mazzo visuale Visual Database Display - Galleria database + Database visuale @@ -8729,7 +8741,7 @@ Più informazioni inserisci, più specifici saranno i risultati. Archidekt - + Archidekt @@ -8857,7 +8869,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Database Display - + Visualizzazione Database @@ -9353,7 +9365,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Do not delete &arrows inside of subphases - + Non eliminare le &freccie al cambio di sottofase @@ -9555,7 +9567,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Search filter... - + Filtro di ricerca... @@ -9578,22 +9590,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Do not display formats with less than this amount of cards in the database - + Non mostrare i formati con meno di questo ammontare di carte nel database Filter mode (AND/OR/NOT conjunctions of filters) - + Modalità di filtraggio (utilizzabile con connettivi logici AND/OR/NOT) Mode: Exact Match - + Modalità: Corrispondenza esatta Mode: Includes - + Modalità: Include @@ -9624,7 +9636,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Filter by name... (Exact match) - + Filtra per nome... (Corrispondenza esatta) @@ -9714,7 +9726,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Visual - + Visuale @@ -9729,12 +9741,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Sort by: - + Ordina per: Filter by: - + Filtra per: @@ -9759,7 +9771,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Table - + Tabella @@ -9767,43 +9779,43 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Group by: - + Raggruppa per: Change how cards are divided into categories/groups. - + Modifica come le carte sono divise in categorie / gruppi. Sort by: - + Ordina per: Click and drag to change the sort order within the groups - + Clicca e trascina per modificare l'ordinamento interno ai gruppi Configure how cards are sorted within their groups - + Configura come le carte sono ordinate all'interno dei loro gruppi Toggle Layout: Overlap - + Disposizione: Sovrapposta Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Modifica come le carte vengono mostrate nelle zone (es. sovrapposte o completamente visibili.) Toggle Layout: Flat - + Disposizione: Piatta @@ -9824,7 +9836,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Type a card name here for suggestions from the database... - + Scrivi un nome di carta qui per avere suggerimenti dal database... @@ -10948,7 +10960,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Reveal Selected Cards to All Players - + Rivela Carta Selezionata a Tutti i Giocatori @@ -11103,12 +11115,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Mulligan (Same hand size) - + Mulligan (Come la mano attuale) Mulligan (Hand size - 1) - + Mulligan (Dimensione della mano - 1) @@ -11138,27 +11150,27 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Sort Hand by Name - + Ordina mano per Nome Sort Hand by Type - + Ordina mano per Tipo Sort Hand by Mana Value - + Ordina mano per Valore di Mana Reveal Hand to All Players - + Rivela Mano a Tutti i Giocatori Reveal Random Card to All Players - + Rivela Carta Casuale a Tutti i Giocatori From 652c8464a7c391884b1295fca96a82c08881cbb8 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 22 Mar 2026 10:28:24 +0100 Subject: [PATCH 04/60] Docker compose: Link to our GHCR hosted `servatrice` image (#6728) * Point to our own image * Point to our own image * Readd build * Readd build --- docker-compose.yml | 2 +- docker-compose.yml.windows | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3d9f9f38f..6cbac61f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: build: context: . dockerfile: Dockerfile - image: servatrice + image: ghcr.io/cockatrice/servatrice:latest depends_on: - mysql ports: diff --git a/docker-compose.yml.windows b/docker-compose.yml.windows index 6663c90fd..3d29b1e5f 100644 --- a/docker-compose.yml.windows +++ b/docker-compose.yml.windows @@ -16,7 +16,7 @@ services: build: context: . dockerfile: Dockerfile - image: servatrice + image: ghcr.io/cockatrice/servatrice:latest depends_on: - mysql ports: From bc219191dbdcf5e870c36470b9c633558f483de9 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 22 Mar 2026 04:32:42 -0700 Subject: [PATCH 05/60] [Game] Refactor options in DlgMoveTopCardsUntil into struct (#6718) --- .../game/dialogs/dlg_move_top_cards_until.cpp | 28 ++++++++----------- .../game/dialogs/dlg_move_top_cards_until.h | 20 +++++++------ cockatrice/src/game/player/player_actions.cpp | 11 +++----- cockatrice/src/game/player/player_actions.h | 5 ++-- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp b/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp index a9db4cef6..2b54452ac 100644 --- a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp +++ b/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp @@ -12,8 +12,7 @@ #include #include -DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, uint _numberOfHits, bool autoPlay) - : QDialog(parent) +DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, const MoveTopCardsUntilOptions &options) : QDialog(parent) { exprLabel = new QLabel(tr("Card name (or search expressions):")); @@ -21,13 +20,13 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, u exprComboBox->setFocus(); exprComboBox->setEditable(true); exprComboBox->setInsertPolicy(QComboBox::InsertAtTop); - exprComboBox->insertItems(0, exprs); + exprComboBox->insertItems(0, options.exprs); exprLabel->setBuddy(exprComboBox); numberOfHitsLabel = new QLabel(tr("Number of hits:")); numberOfHitsEdit = new QSpinBox(this); numberOfHitsEdit->setRange(1, 99); - numberOfHitsEdit->setValue(_numberOfHits); + numberOfHitsEdit->setValue(options.numberOfHits); numberOfHitsLabel->setBuddy(numberOfHitsEdit); auto *grid = new QGridLayout; @@ -35,7 +34,7 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, u grid->addWidget(numberOfHitsEdit, 0, 1); autoPlayCheckBox = new QCheckBox(tr("Auto play hits")); - autoPlayCheckBox->setChecked(autoPlay); + autoPlayCheckBox->setChecked(options.autoPlay); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgMoveTopCardsUntil::validateAndAccept); @@ -118,6 +117,13 @@ QString DlgMoveTopCardsUntil::getExpr() const return exprComboBox->currentText(); } +MoveTopCardsUntilOptions DlgMoveTopCardsUntil::getOptions() const +{ + return {.exprs = getExprs(), + .numberOfHits = numberOfHitsEdit->text().toInt(), + .autoPlay = autoPlayCheckBox->isChecked()}; +} + QStringList DlgMoveTopCardsUntil::getExprs() const { QStringList exprs; @@ -125,14 +131,4 @@ QStringList DlgMoveTopCardsUntil::getExprs() const exprs.append(exprComboBox->itemText(i)); } return exprs; -} - -uint DlgMoveTopCardsUntil::getNumberOfHits() const -{ - return numberOfHitsEdit->text().toUInt(); -} - -bool DlgMoveTopCardsUntil::isAutoPlay() const -{ - return autoPlayCheckBox->isChecked(); -} +} \ No newline at end of file diff --git a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h b/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h index 9a9dc91e7..20ba11c5c 100644 --- a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h +++ b/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h @@ -16,6 +16,13 @@ class FilterString; +struct MoveTopCardsUntilOptions +{ + QStringList exprs = {}; + int numberOfHits = 1; + bool autoPlay = false; +}; + class DlgMoveTopCardsUntil : public QDialog { Q_OBJECT @@ -29,15 +36,12 @@ class DlgMoveTopCardsUntil : public QDialog void validateAndAccept(); bool validateMatchExists(const FilterString &filterString); -public: - explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, - QStringList exprs = QStringList(), - uint numberOfHits = 1, - bool autoPlay = false); - [[nodiscard]] QString getExpr() const; [[nodiscard]] QStringList getExprs() const; - [[nodiscard]] uint getNumberOfHits() const; - [[nodiscard]] bool isAutoPlay() const; + +public: + explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, const MoveTopCardsUntilOptions &options = {}); + [[nodiscard]] QString getExpr() const; + [[nodiscard]] MoveTopCardsUntilOptions getOptions() const; }; #endif // DLG_MOVE_TOP_CARDS_UNTIL_H diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 20e526727..287231402 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -485,22 +485,19 @@ void PlayerActions::actMoveTopCardsUntil() { stopMoveTopCardsUntil(); - DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilExprs, movingCardsUntilNumberOfHits, - movingCardsUntilAutoPlay); + DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilOptions); if (!dlg.exec()) { return; } auto expr = dlg.getExpr(); - movingCardsUntilExprs = dlg.getExprs(); - movingCardsUntilNumberOfHits = dlg.getNumberOfHits(); - movingCardsUntilAutoPlay = dlg.isAutoPlay(); + movingCardsUntilOptions = dlg.getOptions(); if (player->getDeckZone()->getCards().empty()) { stopMoveTopCardsUntil(); } else { movingCardsUntilFilter = FilterString(expr); - movingCardsUntilCounter = movingCardsUntilNumberOfHits; + movingCardsUntilCounter = movingCardsUntilOptions.numberOfHits; movingCardsUntil = true; actMoveTopCardToPlay(); } @@ -512,7 +509,7 @@ void PlayerActions::moveOneCardUntil(CardItem *card) const bool isMatch = card && movingCardsUntilFilter.check(card->getCard().getCardPtr()); - if (isMatch && movingCardsUntilAutoPlay) { + if (isMatch && movingCardsUntilOptions.autoPlay) { // Directly calling playCard will deadlock, since we are already in the middle of processing an event. // Use QTimer::singleShot to queue up the playCard on the event loop. QTimer::singleShot(0, this, [card, this] { playCard(card, false); }); diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h index 1d695578d..d4c6daacf 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/cockatrice/src/game/player/player_actions.h @@ -8,6 +8,7 @@ #ifndef COCKATRICE_PLAYER_ACTIONS_H #define COCKATRICE_PLAYER_ACTIONS_H #include "../dialogs/dlg_create_token.h" +#include "../dialogs/dlg_move_top_cards_until.h" #include "event_processing_options.h" #include "player.h" @@ -178,11 +179,9 @@ private: bool movingCardsUntil; QTimer *moveTopCardTimer; - QStringList movingCardsUntilExprs = {}; - int movingCardsUntilNumberOfHits = 1; - bool movingCardsUntilAutoPlay = false; FilterString movingCardsUntilFilter; int movingCardsUntilCounter = 0; + MoveTopCardsUntilOptions movingCardsUntilOptions; void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); From 414567f8b671c3f7487aa69d9bec96ce5d79d05c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:43:42 +0100 Subject: [PATCH 06/60] Bump docker/login-action from 3 to 4 (#6736) --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 2a4aab1ea..cdcf77e97 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -48,7 +48,7 @@ jobs: - name: Login to GitHub Container Registry if: github.ref_type == 'tag' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} From fa2934373c556e4c485a8748cb5de412ccdae5c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:44:31 +0100 Subject: [PATCH 07/60] Bump microsoft/setup-msbuild from 2 to 3 (#6735) --- .github/workflows/desktop-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 02c3f7aec..820044059 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -341,7 +341,7 @@ jobs: - name: Add msbuild to PATH if: matrix.os == 'Windows' id: add-msbuild - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@v3 with: msbuild-architecture: x64 From 51c684251fa96dbeca4725e4687ae69097e7223d Mon Sep 17 00:00:00 2001 From: tooomm Date: Tue, 24 Mar 2026 00:16:26 +0100 Subject: [PATCH 08/60] Add Docker to release template (#6732) --- .ci/release_template.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/release_template.md b/.ci/release_template.md index 0e6c05165..b0924b92a 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -27,6 +27,8 @@ Available pre-compiled binaries for installation: We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00. General Linux support is available via a flatpak package at Flathub! + +We provide a Docker image for "Servatrice" in GHCR. You can docker pull it or use our Docker Compose files! From aa85a39d6add9b19983e2f5a1426a6f6114100d7 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Tue, 24 Mar 2026 00:16:48 +0100 Subject: [PATCH 09/60] expand local game life total limits (#6730) --- .../src/interface/widgets/dialogs/dlg_local_game_options.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp index a9adfd907..d2d291556 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp @@ -28,8 +28,8 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) startingLifeTotalLabel = new QLabel(tr("Starting life total:"), this); startingLifeTotalEdit = new QSpinBox(this); - startingLifeTotalEdit->setMinimum(1); - startingLifeTotalEdit->setMaximum(99999); + startingLifeTotalEdit->setMinimum(-999999999); + startingLifeTotalEdit->setMaximum(999999999); startingLifeTotalEdit->setValue(20); startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); From 70b41c20958c3a1e87195540756635ea4cdb4891 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Tue, 24 Mar 2026 15:31:34 -0400 Subject: [PATCH 10/60] refactor: extract AbstractPlayerComponent interface for polymorphic player component management. (#6696) Non-QObject polymorphic interface with setShortcutsActive(), setShortcutsInactive(), and retranslateUi(). Uses regular multiple inheritance to avoid diamond inheritance with Qt's MOC. All zone menus, SayMenu, and AbstractCounter implement this interface. PlayerMenu manages them via a managedComponents list with two template helpers (addManagedMenu/registerManagedComponent), replacing individual if-guarded lifecycle calls with a single polymorphic loop. SayMenu now owns its shortcut and translation lifecycle instead of having PlayerMenu manage its title and shortcuts externally. Counters are iterated via Player::getCounters() rather than managedComponents to avoid duplicating the authoritative owner's map. --- cockatrice/src/game/board/abstract_counter.h | 9 +- .../player/menu/abstract_player_component.h | 32 +++++++ .../src/game/player/menu/custom_zone_menu.h | 12 ++- cockatrice/src/game/player/menu/grave_menu.h | 9 +- cockatrice/src/game/player/menu/hand_menu.h | 9 +- .../src/game/player/menu/library_menu.h | 9 +- .../src/game/player/menu/player_menu.cpp | 89 ++++--------------- cockatrice/src/game/player/menu/player_menu.h | 27 +++++- cockatrice/src/game/player/menu/rfg_menu.h | 11 ++- cockatrice/src/game/player/menu/say_menu.cpp | 34 ++++++- cockatrice/src/game/player/menu/say_menu.h | 11 ++- .../src/game/player/menu/sideboard_menu.h | 10 ++- .../src/game/player/menu/utility_menu.h | 10 ++- 13 files changed, 164 insertions(+), 108 deletions(-) create mode 100644 cockatrice/src/game/player/menu/abstract_player_component.h diff --git a/cockatrice/src/game/board/abstract_counter.h b/cockatrice/src/game/board/abstract_counter.h index ea13cb00f..074650d54 100644 --- a/cockatrice/src/game/board/abstract_counter.h +++ b/cockatrice/src/game/board/abstract_counter.h @@ -8,6 +8,7 @@ #define COUNTER_H #include "../../interface/widgets/menus/tearoff_menu.h" +#include "../player/menu/abstract_player_component.h" #include #include @@ -18,7 +19,7 @@ class QKeyEvent; class QMenu; class QString; -class AbstractCounter : public QObject, public QGraphicsItem +class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPlayerComponent { Q_OBJECT Q_INTERFACES(QGraphicsItem) @@ -56,10 +57,10 @@ public: QGraphicsItem *parent = nullptr); ~AbstractCounter() override; - void retranslateUi(); + void retranslateUi() override; void setValue(int _value); - void setShortcutsActive(); - void setShortcutsInactive(); + void setShortcutsActive() override; + void setShortcutsInactive() override; void delCounter(); QMenu *getMenu() const diff --git a/cockatrice/src/game/player/menu/abstract_player_component.h b/cockatrice/src/game/player/menu/abstract_player_component.h new file mode 100644 index 000000000..989300d41 --- /dev/null +++ b/cockatrice/src/game/player/menu/abstract_player_component.h @@ -0,0 +1,32 @@ +/** + * @file abstract_player_component.h + * @ingroup GameMenusPlayers + * @brief Polymorphic interface for player-bound UI components managed by PlayerMenu. + */ + +#ifndef COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H +#define COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H + +/** + * @brief Interface for player-bound UI components that need shortcut and translation lifecycle management. + * + * Not a QObject — avoids diamond inheritance with Qt's MOC. Each concrete component + * inherits QObject through its Qt base class (QMenu, TearOffMenu, QGraphicsItem, etc.) + * and this interface through regular multiple inheritance. + */ +class AbstractPlayerComponent +{ +public: + virtual ~AbstractPlayerComponent() = default; + + /// Bind keyboard shortcuts. Called when this player gains focus. + virtual void setShortcutsActive() = 0; + + /// Unbind keyboard shortcuts. Called when this player loses focus. + virtual void setShortcutsInactive() = 0; + + /// Retranslate all user-visible strings. Called on language change. + virtual void retranslateUi() = 0; +}; + +#endif // COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H diff --git a/cockatrice/src/game/player/menu/custom_zone_menu.h b/cockatrice/src/game/player/menu/custom_zone_menu.h index 0944029f4..c4e66754e 100644 --- a/cockatrice/src/game/player/menu/custom_zone_menu.h +++ b/cockatrice/src/game/player/menu/custom_zone_menu.h @@ -7,15 +7,23 @@ #ifndef COCKATRICE_CUSTOM_ZONE_MENU_H #define COCKATRICE_CUSTOM_ZONE_MENU_H +#include "abstract_player_component.h" + #include class Player; -class CustomZoneMenu : public QMenu +class CustomZoneMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: explicit CustomZoneMenu(Player *player); - void retranslateUi(); + void retranslateUi() override; + void setShortcutsActive() override + { + } + void setShortcutsInactive() override + { + } private: Player *player; diff --git a/cockatrice/src/game/player/menu/grave_menu.h b/cockatrice/src/game/player/menu/grave_menu.h index faaf497b6..429173afa 100644 --- a/cockatrice/src/game/player/menu/grave_menu.h +++ b/cockatrice/src/game/player/menu/grave_menu.h @@ -8,12 +8,13 @@ #define COCKATRICE_GRAVE_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_player_component.h" #include #include class Player; -class GraveyardMenu : public TearOffMenu +class GraveyardMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT signals: @@ -25,9 +26,9 @@ public: void createViewActions(); void populateRevealRandomMenuWithActivePlayers(); void onRevealRandomTriggered(); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; QMenu *mRevealRandomGraveyardCard = nullptr; QMenu *moveGraveMenu = nullptr; diff --git a/cockatrice/src/game/player/menu/hand_menu.h b/cockatrice/src/game/player/menu/hand_menu.h index 51e071a62..76434cc98 100644 --- a/cockatrice/src/game/player/menu/hand_menu.h +++ b/cockatrice/src/game/player/menu/hand_menu.h @@ -8,6 +8,7 @@ #define COCKATRICE_HAND_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_player_component.h" #include #include @@ -15,7 +16,7 @@ class Player; class PlayerActions; -class HandMenu : public TearOffMenu +class HandMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT @@ -31,9 +32,9 @@ public: return mRevealRandomHandCard; } - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; private slots: void populateRevealHandMenuWithActivePlayers(); diff --git a/cockatrice/src/game/player/menu/library_menu.h b/cockatrice/src/game/player/menu/library_menu.h index c0883107c..444e8f516 100644 --- a/cockatrice/src/game/player/menu/library_menu.h +++ b/cockatrice/src/game/player/menu/library_menu.h @@ -8,6 +8,7 @@ #define COCKATRICE_LIBRARY_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_player_component.h" #include #include @@ -15,7 +16,7 @@ class Player; class PlayerActions; -class LibraryMenu : public TearOffMenu +class LibraryMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT public slots: @@ -28,15 +29,15 @@ public: void createShuffleActions(); void createMoveActions(); void createViewActions(); - void retranslateUi(); + void retranslateUi() override; void populateRevealLibraryMenuWithActivePlayers(); void populateLendLibraryMenuWithActivePlayers(); void populateRevealTopCardMenuWithActivePlayers(); void onRevealLibraryTriggered(); void onLendLibraryTriggered(); void onRevealTopCardTriggered(); - void setShortcutsActive(); - void setShortcutsInactive(); + void setShortcutsActive() override; + void setShortcutsInactive() override; [[nodiscard]] bool isAlwaysRevealTopCardChecked() const { diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp index 3016a727f..7786ec3fc 100644 --- a/cockatrice/src/game/player/menu/player_menu.cpp +++ b/cockatrice/src/game/player/menu/player_menu.cpp @@ -15,33 +15,24 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player) playerMenu = new TearOffMenu(); if (player->getPlayerInfo()->getLocalOrJudge()) { - handMenu = new HandMenu(player, player->getPlayerActions(), playerMenu); - playerMenu->addMenu(handMenu); - - libraryMenu = new LibraryMenu(player, playerMenu); - playerMenu->addMenu(libraryMenu); + handMenu = addManagedMenu(player, player->getPlayerActions(), playerMenu); + libraryMenu = addManagedMenu(player, playerMenu); } else { handMenu = nullptr; libraryMenu = nullptr; } - graveMenu = new GraveyardMenu(player, playerMenu); - playerMenu->addMenu(graveMenu); - - rfgMenu = new RfgMenu(player, playerMenu); - playerMenu->addMenu(rfgMenu); + graveMenu = addManagedMenu(player, playerMenu); + rfgMenu = addManagedMenu(player, playerMenu); if (player->getPlayerInfo()->getLocalOrJudge()) { - sideboardMenu = new SideboardMenu(player, playerMenu); - playerMenu->addMenu(sideboardMenu); - - customZonesMenu = new CustomZoneMenu(player); - playerMenu->addMenu(customZonesMenu); + sideboardMenu = addManagedMenu(player, playerMenu); + customZonesMenu = addManagedMenu(player); playerMenu->addSeparator(); countersMenu = playerMenu->addMenu(QString()); - utilityMenu = new UtilityMenu(player, playerMenu); + utilityMenu = createManagedComponent(player, playerMenu); } else { sideboardMenu = nullptr; customZonesMenu = nullptr; @@ -50,8 +41,7 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player) } if (player->getPlayerInfo()->getLocal()) { - sayMenu = new SayMenu(player); - playerMenu->addMenu(sayMenu); + sayMenu = addManagedMenu(player); } else { sayMenu = nullptr; } @@ -99,40 +89,18 @@ void PlayerMenu::retranslateUi() { playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName())); - if (handMenu) { - handMenu->retranslateUi(); - } - if (libraryMenu) { - libraryMenu->retranslateUi(); - } - - graveMenu->retranslateUi(); - rfgMenu->retranslateUi(); - - if (sideboardMenu) { - sideboardMenu->retranslateUi(); + for (auto *component : managedComponents) { + component->retranslateUi(); } if (countersMenu) { countersMenu->setTitle(tr("&Counters")); } - if (customZonesMenu) { - customZonesMenu->retranslateUi(); - } - QMapIterator counterIterator(player->getCounters()); while (counterIterator.hasNext()) { counterIterator.next().value()->retranslateUi(); } - - if (utilityMenu) { - utilityMenu->retranslateUi(); - } - - if (sayMenu) { - sayMenu->setTitle(tr("S&ay")); - } } void PlayerMenu::refreshShortcuts() @@ -153,52 +121,29 @@ void PlayerMenu::setShortcutsActive() { shortcutsActive = true; - if (handMenu) { - handMenu->setShortcutsActive(); - } - if (libraryMenu) { - libraryMenu->setShortcutsActive(); - } - graveMenu->setShortcutsActive(); - // No shortcuts for RfgMenu yet - - if (sideboardMenu) { - sideboardMenu->setShortcutsActive(); + for (auto *component : managedComponents) { + component->setShortcutsActive(); } + // Counters implement AbstractPlayerComponent but are iterated via Player::counters + // (the authoritative source) rather than managedComponents to avoid a redundant + // list that must stay in sync with the map. QMapIterator counterIterator(player->getCounters()); while (counterIterator.hasNext()) { counterIterator.next().value()->setShortcutsActive(); } - - if (utilityMenu) { - utilityMenu->setShortcutsActive(); - } } void PlayerMenu::setShortcutsInactive() { shortcutsActive = false; - if (handMenu) { - handMenu->setShortcutsInactive(); - } - if (libraryMenu) { - libraryMenu->setShortcutsInactive(); - } - graveMenu->setShortcutsInactive(); - // No shortcuts for RfgMenu yet - - if (sideboardMenu) { - sideboardMenu->setShortcutsInactive(); + for (auto *component : managedComponents) { + component->setShortcutsInactive(); } QMapIterator counterIterator(player->getCounters()); while (counterIterator.hasNext()) { counterIterator.next().value()->setShortcutsInactive(); } - - if (utilityMenu) { - utilityMenu->setShortcutsInactive(); - } } \ No newline at end of file diff --git a/cockatrice/src/game/player/menu/player_menu.h b/cockatrice/src/game/player/menu/player_menu.h index 882bfedc5..5fce27158 100644 --- a/cockatrice/src/game/player/menu/player_menu.h +++ b/cockatrice/src/game/player/menu/player_menu.h @@ -1,7 +1,7 @@ /** * @file player_menu.h * @ingroup GameMenusPlayers - * @brief TODO: Document this. + * @brief Orchestrates lifecycle management for all player-bound UI components. */ #ifndef COCKATRICE_PLAYER_MENU_H @@ -18,6 +18,7 @@ #include "sideboard_menu.h" #include "utility_menu.h" +#include #include #include @@ -37,6 +38,7 @@ private slots: public: PlayerMenu(Player *player); + /// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters(). void retranslateUi(); QMenu *updateCardMenu(const CardItem *card); @@ -66,7 +68,9 @@ public: return shortcutsActive; } + /// Delegates to all managedComponents, plus counters separately. void setShortcutsActive(); + /// Delegates to all managedComponents, plus counters separately. void setShortcutsInactive(); private: @@ -82,9 +86,26 @@ private: SayMenu *sayMenu; CustomZoneMenu *customZonesMenu; - bool shortcutsActive; + /// Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via player->getCounters(). + QList managedComponents; + bool shortcutsActive = false; - void initSayMenu(); + /// Creates component, adds it as a submenu of playerMenu, and registers in managedComponents. + template MenuT *addManagedMenu(Args &&...args) + { + auto *menu = new MenuT(std::forward(args)...); + playerMenu->addMenu(menu); + managedComponents.append(menu); + return menu; + } + + /// Creates component and registers in managedComponents, but does NOT add it as a submenu. + template ComponentT *createManagedComponent(Args &&...args) + { + auto *component = new ComponentT(std::forward(args)...); + managedComponents.append(component); + return component; + } }; #endif // COCKATRICE_PLAYER_MENU_H diff --git a/cockatrice/src/game/player/menu/rfg_menu.h b/cockatrice/src/game/player/menu/rfg_menu.h index 0b4623d2a..8f79b2f4a 100644 --- a/cockatrice/src/game/player/menu/rfg_menu.h +++ b/cockatrice/src/game/player/menu/rfg_menu.h @@ -8,19 +8,26 @@ #define COCKATRICE_RFG_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_player_component.h" #include #include class Player; -class RfgMenu : public TearOffMenu +class RfgMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT public: explicit RfgMenu(Player *player, QWidget *parent = nullptr); void createMoveActions(); void createViewActions(); - void retranslateUi(); + void retranslateUi() override; + void setShortcutsActive() override + { + } + void setShortcutsInactive() override + { + } QMenu *moveRfgMenu = nullptr; diff --git a/cockatrice/src/game/player/menu/say_menu.cpp b/cockatrice/src/game/player/menu/say_menu.cpp index 3c4802aa5..116fba49a 100644 --- a/cockatrice/src/game/player/menu/say_menu.cpp +++ b/cockatrice/src/game/player/menu/say_menu.cpp @@ -8,6 +8,31 @@ SayMenu::SayMenu(Player *_player) : player(_player) { connect(&SettingsCache::instance().messages(), &MessageSettings::messageMacrosChanged, this, &SayMenu::initSayMenu); initSayMenu(); + retranslateUi(); +} + +void SayMenu::retranslateUi() +{ + setTitle(tr("S&ay")); +} + +void SayMenu::setShortcutsActive() +{ + shortcutsActive = true; + + const auto menuActions = actions(); + for (int i = 0; i < menuActions.size() && i < 10; ++i) { + menuActions[i]->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10))); + } +} + +void SayMenu::setShortcutsInactive() +{ + shortcutsActive = false; + + for (auto *action : actions()) { + action->setShortcut(QKeySequence()); + } } void SayMenu::initSayMenu() @@ -19,10 +44,11 @@ void SayMenu::initSayMenu() for (int i = 0; i < count; ++i) { auto *newAction = new QAction(SettingsCache::instance().messages().getMessageAt(i), this); - if (i < 10) { - newAction->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10))); - } connect(newAction, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actSayMessage); addAction(newAction); } -} \ No newline at end of file + + if (shortcutsActive) { + setShortcutsActive(); + } +} diff --git a/cockatrice/src/game/player/menu/say_menu.h b/cockatrice/src/game/player/menu/say_menu.h index 5dbde2277..fadf5f368 100644 --- a/cockatrice/src/game/player/menu/say_menu.h +++ b/cockatrice/src/game/player/menu/say_menu.h @@ -7,18 +7,27 @@ #ifndef COCKATRICE_SAY_MENU_H #define COCKATRICE_SAY_MENU_H +#include "abstract_player_component.h" + #include class Player; -class SayMenu : public QMenu +class SayMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: explicit SayMenu(Player *player); + + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; + +private slots: void initSayMenu(); private: Player *player; + bool shortcutsActive = false; }; #endif // COCKATRICE_SAY_MENU_H diff --git a/cockatrice/src/game/player/menu/sideboard_menu.h b/cockatrice/src/game/player/menu/sideboard_menu.h index 22d5a2d69..4a77d1b52 100644 --- a/cockatrice/src/game/player/menu/sideboard_menu.h +++ b/cockatrice/src/game/player/menu/sideboard_menu.h @@ -7,18 +7,20 @@ #ifndef COCKATRICE_SIDEBOARD_MENU_H #define COCKATRICE_SIDEBOARD_MENU_H +#include "abstract_player_component.h" + #include class Player; -class SideboardMenu : public QMenu +class SideboardMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: explicit SideboardMenu(Player *player, QMenu *playerMenu); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; private: Player *player; diff --git a/cockatrice/src/game/player/menu/utility_menu.h b/cockatrice/src/game/player/menu/utility_menu.h index ff57e7252..f6577d7d1 100644 --- a/cockatrice/src/game/player/menu/utility_menu.h +++ b/cockatrice/src/game/player/menu/utility_menu.h @@ -7,17 +7,19 @@ #ifndef COCKATRICE_UTILITY_MENU_H #define COCKATRICE_UTILITY_MENU_H +#include "abstract_player_component.h" + #include class Player; -class UtilityMenu : public QMenu +class UtilityMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public slots: void populatePredefinedTokensMenu(); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; public: explicit UtilityMenu(Player *player, QMenu *playerMenu); From 94ea574c76ea214d7d74910b71795f225b28b75e Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Tue, 24 Mar 2026 16:45:52 -0400 Subject: [PATCH 11/60] Add moveToTable context menu action and extract tableRowToGridY helper (#6738) Adds a Table option to the Move menu, allowing cards to be moved directly to the battlefield from any zone. Extracts the repeated tableRow-to-grid-Y conversion logic into TableZone::tableRowToGridY(), consolidating five call sites and fixing a latent bug where cards with tableRow > 2 could land on the wrong row. --- .../src/client/settings/shortcuts_settings.h | 3 ++ .../src/game/player/card_menu_action_type.h | 5 ++- cockatrice/src/game/player/menu/move_menu.cpp | 9 +++- cockatrice/src/game/player/menu/move_menu.h | 1 + cockatrice/src/game/player/player_actions.cpp | 45 ++++++++++++++----- cockatrice/src/game/zones/table_zone.cpp | 8 ++++ cockatrice/src/game/zones/table_zone.h | 7 +++ 7 files changed, 64 insertions(+), 14 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 51615745b..d0849042b 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -563,6 +563,9 @@ private: {"Player/aMoveToTopLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"), parseSequenceString(""), ShortcutGroup::Move_selected)}, + {"Player/aMoveToTable", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"), + parseSequenceString(""), + ShortcutGroup::Move_selected)}, {"Player/aViewHand", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)}, {"Player/aViewGraveyard", diff --git a/cockatrice/src/game/player/card_menu_action_type.h b/cockatrice/src/game/player/card_menu_action_type.h index aec6d6397..1b63674fa 100644 --- a/cockatrice/src/game/player/card_menu_action_type.h +++ b/cockatrice/src/game/player/card_menu_action_type.h @@ -9,17 +9,20 @@ enum CardMenuActionType { + // Per-card attribute actions (must be <= cmClone for cardMenuAction() dispatch) cmTap, cmUntap, cmDoesntUntap, cmFlip, cmPeek, cmClone, + // Move actions (must be > cmClone for cardMenuAction() dispatch) cmMoveToTopLibrary, cmMoveToBottomLibrary, cmMoveToHand, cmMoveToGraveyard, - cmMoveToExile + cmMoveToExile, + cmMoveToTable }; #endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H diff --git a/cockatrice/src/game/player/menu/move_menu.cpp b/cockatrice/src/game/player/menu/move_menu.cpp index d27e16009..91e2d8d10 100644 --- a/cockatrice/src/game/player/menu/move_menu.cpp +++ b/cockatrice/src/game/player/menu/move_menu.cpp @@ -11,6 +11,8 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) aMoveToBottomLibrary = new QAction(this); aMoveToBottomLibrary->setData(cmMoveToBottomLibrary); aMoveToXfromTopOfLibrary = new QAction(this); + aMoveToTable = new QAction(this); + aMoveToTable->setData(cmMoveToTable); aMoveToGraveyard = new QAction(this); aMoveToHand = new QAction(this); aMoveToHand->setData(cmMoveToHand); @@ -22,6 +24,7 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) connect(aMoveToBottomLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToXfromTopOfLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actMoveCardXCardsFromTop); + connect(aMoveToTable, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToHand, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToGraveyard, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToExile, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); @@ -30,6 +33,8 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) addAction(aMoveToXfromTopOfLibrary); addAction(aMoveToBottomLibrary); addSeparator(); + addAction(aMoveToTable); + addSeparator(); addAction(aMoveToHand); addSeparator(); addAction(aMoveToGraveyard); @@ -47,6 +52,7 @@ void MoveMenu::setShortcutsActive() aMoveToTopLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToTopLibrary")); aMoveToBottomLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToBottomLibrary")); + aMoveToTable->setShortcuts(shortcuts.getShortcut("Player/aMoveToTable")); aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand")); aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard")); aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile")); @@ -57,7 +63,8 @@ void MoveMenu::retranslateUi() aMoveToTopLibrary->setText(tr("&Top of library in random order")); aMoveToXfromTopOfLibrary->setText(tr("X cards from the top of library...")); aMoveToBottomLibrary->setText(tr("&Bottom of library in random order")); + aMoveToTable->setText(tr("T&able")); aMoveToHand->setText(tr("&Hand")); aMoveToGraveyard->setText(tr("&Graveyard")); aMoveToExile->setText(tr("&Exile")); -} \ No newline at end of file +} diff --git a/cockatrice/src/game/player/menu/move_menu.h b/cockatrice/src/game/player/menu/move_menu.h index 5bf657fa4..dc39cb6a5 100644 --- a/cockatrice/src/game/player/menu/move_menu.h +++ b/cockatrice/src/game/player/menu/move_menu.h @@ -23,6 +23,7 @@ public: QAction *aMoveToBottomLibrary = nullptr; QAction *aMoveToHand = nullptr; + QAction *aMoveToTable = nullptr; QAction *aMoveToGraveyard = nullptr; QAction *aMoveToExile = nullptr; }; diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 287231402..cee04ac6b 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -75,7 +75,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) cmd.set_y(0); } else { tableRow = faceDown ? 2 : info.getUiAttributes().tableRow; - QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow)); + QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow)); cardToMove->set_face_down(faceDown); if (!faceDown) { cardToMove->set_pt(info.getPowTough().toStdString()); @@ -114,12 +114,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) const CardInfo &info = exactCard.getInfo(); int tableRow = faceDown ? 2 : info.getUiAttributes().tableRow; - // default instant/sorcery cards to the noncreatures row - if (tableRow > 2) { - tableRow = 1; - } - - QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow)); + QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow)); cardToMove->set_face_down(faceDown); if (!faceDown) { cardToMove->set_pt(info.getPowTough().toStdString()); @@ -866,7 +861,7 @@ void PlayerActions::actCreateToken() ExactCard correctedCard = CardDatabaseManager::query()->guessCard({lastTokenInfo.name, lastTokenInfo.providerId}); if (correctedCard) { lastTokenInfo.name = correctedCard.getName(); - lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard.getInfo().getUiAttributes().tableRow); + lastTokenTableRow = TableZone::tableRowToGridY(correctedCard.getInfo().getUiAttributes().tableRow); if (lastTokenInfo.pt.isEmpty()) { lastTokenInfo.pt = correctedCard.getInfo().getPowTough(); } @@ -917,7 +912,7 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo) .providerId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())}; - lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow); + lastTokenTableRow = TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow); utilityMenu->setAndEnableCreateAnotherTokenAction(tr("C&reate another %1 token").arg(lastTokenInfo.name)); } @@ -1085,9 +1080,7 @@ void PlayerActions::createCard(const CardItem *sourceCard, return; } - // get the target token's location - // TODO: Define this QPoint into its own function along with the one below - QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow)); + QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow)); // create the token for the related card Command_CreateToken cmd; @@ -1930,6 +1923,34 @@ void PlayerActions::cardMenuAction() commandList.append(cmd); break; } + case cmMoveToTable: { + // Each card needs its own command because table row, pt, and cipt vary per card + for (const auto &card : cardList) { + auto *cmd = new Command_MoveCard; + cmd->set_start_player_id(startPlayerId); + cmd->set_start_zone(startZone.toStdString()); + cmd->set_target_player_id(player->getPlayerInfo()->getId()); + cmd->set_target_zone(ZoneNames::TABLE); + cmd->set_x(-1); + + CardToMove *ctm = cmd->mutable_cards_to_move()->add_card(); + ctm->set_card_id(card->getId()); + ctm->set_face_down(false); + + int tableRow = 0; + ExactCard exactCard = card->getCard(); + if (exactCard) { + const CardInfo &info = exactCard.getInfo(); + tableRow = info.getUiAttributes().tableRow; + ctm->set_pt(info.getPowTough().toStdString()); + ctm->set_tapped(info.getUiAttributes().cipt); + } + + cmd->set_y(TableZone::tableRowToGridY(tableRow)); + commandList.append(cmd); + } + break; + } default: break; } diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index b6ac2150b..2a382fafe 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -382,3 +382,11 @@ int TableZone::clampValidTableRow(const int row) return TABLEROWS - 1; return row; } + +int TableZone::tableRowToGridY(int tableRow) +{ + if (tableRow > 2) { + tableRow = 1; + } + return clampValidTableRow(2 - tableRow); +} diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index 61eb48d7b..7a53a9eb4 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -151,6 +151,13 @@ public: static int clampValidTableRow(const int row); + /** + * Converts a card's logical table row (0=creatures, 1=noncreatures, 2=lands) + * to the corresponding grid Y coordinate. Cards with tableRow > 2 (e.g., + * instants/sorceries) default to the noncreatures row. + */ + static int tableRowToGridY(int tableRow); + /** Resizes the TableZone in case CardItems are within or outside of the TableZone constraints. From 5ef428b9d0fec65b1b264023ce2b2196945ea7c5 Mon Sep 17 00:00:00 2001 From: scotland0208 Date: Wed, 25 Mar 2026 16:15:08 -0500 Subject: [PATCH 12/60] Add visual indicator to toggle untap button (#6737) * Add visual indicator to toggle untap button * Rename button to match tooltip * Change name of string in shortcut settings --- cockatrice/src/client/settings/shortcuts_settings.h | 2 +- cockatrice/src/game/player/menu/card_menu.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index d0849042b..1de73c165 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -501,7 +501,7 @@ private: {"Player/aUntapAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Untap All"), parseSequenceString("Ctrl+U"), ShortcutGroup::Playing_Area)}, - {"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Untap"), + {"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping"), parseSequenceString("Alt+U"), ShortcutGroup::Playing_Area)}, {"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"), diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp index cd77c2968..f2479e1da 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game/player/menu/card_menu.cpp @@ -35,6 +35,8 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive connect(aTap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction); aDoesntUntap = new QAction(this); aDoesntUntap->setData(cmDoesntUntap); + aDoesntUntap->setCheckable(true); + aDoesntUntap->setChecked(card != nullptr && card->getDoesntUntap()); connect(aDoesntUntap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction); aAttach = new QAction(this); connect(aAttach, &QAction::triggered, playerActions, &PlayerActions::actAttach); @@ -449,7 +451,7 @@ void CardMenu::retranslateUi() aRevealToAll->setText(tr("&All players")); //: Turn sideways or back again aTap->setText(tr("&Tap / Untap")); - aDoesntUntap->setText(tr("Toggle &normal untapping")); + aDoesntUntap->setText(tr("Skip &untapping")); //: Turn face up/face down aFlip->setText(tr("T&urn Over")); // Only the user facing names in client got renamed to "turn over" // All code and proto bits are still unchanged (flip) for compatibility reasons From dd053c76dfc357896b7a49a5ab636161b32a7aa7 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Wed, 25 Mar 2026 18:03:59 -0400 Subject: [PATCH 13/60] [Game] Improve context menus and fix face-down play from stack (#6739) Reorganize card context menus across table, stack, and graveyard/exile zones for better consistency: promote Draw Arrow and Clone actions, move related card entries to the bottom, add Play/Play Face Down to the stack menu, and flatten if/else blocks with early returns. Also fix playCard() ignoring the faceDown flag when routing instants/sorceries from the stack, which sent them to the graveyard instead of the table. --- cockatrice/src/game/player/menu/card_menu.cpp | 79 ++++++++++--------- cockatrice/src/game/player/player_actions.cpp | 2 +- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp index f2479e1da..66ca5e46b 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game/player/menu/card_menu.cpp @@ -110,6 +110,7 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive if (revealedCard) { addAction(aHide); + addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); @@ -148,16 +149,14 @@ void CardMenu::createTableMenu(bool canModifyCard) { // Card is on the battlefield if (!canModifyCard) { - addRelatedCardView(); - addRelatedCardActions(); - - addSeparator(); addAction(aDrawArrow); addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); addAction(aSelectRow); + addRelatedCardView(); + addRelatedCardActions(); return; } @@ -167,10 +166,9 @@ void CardMenu::createTableMenu(bool canModifyCard) if (card->getFaceDown()) { addAction(aPeek); } - - addRelatedCardView(); - addRelatedCardActions(); - + addSeparator(); + addAction(aClone); + addMenu(new MoveMenu(player)); addSeparator(); addAction(aAttach); if (card->getAttachedTo()) { @@ -181,9 +179,6 @@ void CardMenu::createTableMenu(bool canModifyCard) addMenu(new PtMenu(player)); addAction(aSetAnnotation); addSeparator(); - addAction(aClone); - addMenu(new MoveMenu(player)); - addSeparator(); addAction(aSelectAll); addAction(aSelectRow); @@ -199,27 +194,34 @@ void CardMenu::createTableMenu(bool canModifyCard) } addSeparator(); addMenu(mCardCounters); + addRelatedCardView(); + addRelatedCardActions(); } void CardMenu::createStackMenu(bool canModifyCard) { // Card is on the stack - if (canModifyCard) { - addAction(aAttach); - addAction(aDrawArrow); - addSeparator(); - addAction(aClone); - addMenu(new MoveMenu(player)); - addSeparator(); - addAction(aSelectAll); - } else { + if (!canModifyCard) { addAction(aDrawArrow); addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); + addRelatedCardView(); + addRelatedCardActions(); + return; } + addAction(aPlay); + addAction(aPlayFacedown); + addSeparator(); + addAction(aClone); + addMenu(new MoveMenu(player)); + addSeparator(); + addAction(aAttach); + addAction(aDrawArrow); + addSeparator(); + addAction(aSelectAll); addRelatedCardView(); addRelatedCardActions(); } @@ -227,29 +229,29 @@ void CardMenu::createStackMenu(bool canModifyCard) void CardMenu::createGraveyardOrExileMenu(bool canModifyCard) { // Card is in the graveyard or exile - if (canModifyCard) { - addAction(aPlay); - addAction(aPlayFacedown); - - addSeparator(); - addAction(aClone); - addMenu(new MoveMenu(player)); - addSeparator(); - addAction(aSelectAll); - addAction(aSelectColumn); - - addSeparator(); - addAction(aAttach); + if (!canModifyCard) { addAction(aDrawArrow); - } else { + addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); addAction(aSelectColumn); - addSeparator(); - addAction(aDrawArrow); + addRelatedCardView(); + addRelatedCardActions(); + return; } + addAction(aPlay); + addAction(aPlayFacedown); + addSeparator(); + addAction(aClone); + addMenu(new MoveMenu(player)); + addSeparator(); + addAction(aAttach); + addAction(aDrawArrow); + addSeparator(); + addAction(aSelectAll); + addAction(aSelectColumn); addRelatedCardView(); addRelatedCardActions(); } @@ -259,12 +261,11 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard) if (!canModifyCard) { addAction(aDrawArrow); addSeparator(); - addRelatedCardView(); - addRelatedCardActions(); - addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); + addRelatedCardView(); + addRelatedCardActions(); return; } diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index cee04ac6b..ca0967636 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -64,7 +64,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) int tableRow = info.getUiAttributes().tableRow; bool playToStack = SettingsCache::instance().getPlayToStack(); QString currentZone = card->getZone()->getName(); - if (currentZone == ZoneNames::STACK && tableRow == 3) { + if (!faceDown && currentZone == ZoneNames::STACK && tableRow == 3) { cmd.set_target_zone(ZoneNames::GRAVE); cmd.set_x(0); cmd.set_y(0); From 74cce5ccb2fbae2c4a6a424600b6ca4b155f2402 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:12:49 -0700 Subject: [PATCH 14/60] [SettingsManager] Properly handle multithreaded access (#6747) --- .../client/settings/card_counter_settings.cpp | 4 +++- .../settings/settings_manager.cpp | 24 ++++++++++++++++--- .../libcockatrice/settings/settings_manager.h | 5 +++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/cockatrice/src/client/settings/card_counter_settings.cpp b/cockatrice/src/client/settings/card_counter_settings.cpp index 71ce4cfc6..399365c99 100644 --- a/cockatrice/src/client/settings/card_counter_settings.cpp +++ b/cockatrice/src/client/settings/card_counter_settings.cpp @@ -11,6 +11,8 @@ CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *p void CardCounterSettings::setColor(int counterId, const QColor &color) { + QSettings settings = getSettings(); + QString key = QString("cards/counters/%1/color").arg(counterId); if (settings.value(key).value() == color) @@ -36,7 +38,7 @@ QColor CardCounterSettings::color(int counterId) const defaultColor = QColor::fromHsv(h, s, v); } - return settings.value(QString("cards/counters/%1/color").arg(counterId), defaultColor).value(); + return getSettings().value(QString("cards/counters/%1/color").arg(counterId), defaultColor).value(); } QString CardCounterSettings::displayName(int counterId) const diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp index ede5a2027..3f3deb638 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp @@ -1,16 +1,22 @@ #include "settings_manager.h" -SettingsManager::SettingsManager(const QString &settingPath, +SettingsManager::SettingsManager(const QString &_settingPath, const QString &_defaultGroup, const QString &_defaultSubGroup, QObject *parent) - : QObject(parent), settings(settingPath, QSettings::IniFormat), defaultGroup(_defaultGroup), - defaultSubGroup(_defaultSubGroup) + : QObject(parent), settingPath(_settingPath), defaultGroup(_defaultGroup), defaultSubGroup(_defaultSubGroup) { } +QSettings SettingsManager::getSettings() const +{ + return QSettings(settingPath, QSettings::IniFormat); +} + void SettingsManager::setValue(const QVariant &value, const QString &name) { + auto settings = getSettings(); + if (!defaultGroup.isEmpty()) { settings.beginGroup(defaultGroup); } @@ -35,6 +41,8 @@ void SettingsManager::setValue(const QVariant &value, const QString &group, const QString &subGroup) { + auto settings = getSettings(); + if (!group.isEmpty()) { settings.beginGroup(group); } @@ -56,6 +64,8 @@ void SettingsManager::setValue(const QVariant &value, void SettingsManager::deleteValue(const QString &name) { + auto settings = getSettings(); + if (!defaultGroup.isEmpty()) { settings.beginGroup(defaultGroup); } @@ -77,6 +87,8 @@ void SettingsManager::deleteValue(const QString &name) void SettingsManager::deleteValue(const QString &name, const QString &group, const QString &subGroup) { + auto settings = getSettings(); + if (!group.isEmpty()) { settings.beginGroup(group); } @@ -98,6 +110,8 @@ void SettingsManager::deleteValue(const QString &name, const QString &group, con QVariant SettingsManager::getValue(const QString &name) { + auto settings = getSettings(); + if (!defaultGroup.isEmpty()) { settings.beginGroup(defaultGroup); } @@ -121,6 +135,8 @@ QVariant SettingsManager::getValue(const QString &name) QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup) { + auto settings = getSettings(); + if (!group.isEmpty()) { settings.beginGroup(group); } @@ -147,5 +163,7 @@ QVariant SettingsManager::getValue(const QString &name, const QString &group, co */ void SettingsManager::sync() { + auto settings = getSettings(); + settings.sync(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index 3592d8f8c..18abb8dbf 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -24,9 +24,12 @@ public: void sync(); protected: - QSettings settings; + QString settingPath; QString defaultGroup; QString defaultSubGroup; + + QSettings getSettings() const; + void setValue(const QVariant &value, const QString &name); void setValue(const QVariant &value, const QString &name, const QString &group, const QString &subGroup = QString()); From abf6e72ad1b341feda04676554a751cdec97169c Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 27 Mar 2026 18:08:51 +0100 Subject: [PATCH 15/60] add a nulcheck in the card item animation timer (#6740) --- cockatrice/src/game/board/card_item.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index 62de4a02e..cf3c7db20 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -460,6 +460,9 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) bool CardItem::animationEvent() { + if (owner == nullptr) { + return false; + } int rotation = ROTATION_DEGREES_PER_FRAME; bool animationIncomplete = true; if (!tapped) From 42bd8164a0b9170a3ecfe68b43ca399e1e08e4a7 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 27 Mar 2026 18:10:29 +0100 Subject: [PATCH 16/60] remove hardcoded white in vde banner widget (#6684) * remove hardcoded white in vde banner widget * set text color to white on higher opacities --- .../widgets/general/display/banner_widget.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp index f03869a4d..5de5457ea 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp @@ -7,8 +7,8 @@ #include #include -BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency) - : QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency, 100)) +BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency_) + : QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency_, 100)) { auto layout = new QHBoxLayout(this); @@ -18,7 +18,12 @@ BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation // Create the banner label and set properties bannerLabel = new QLabel(text, this); bannerLabel->setAlignment(Qt::AlignCenter); - bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold; color: white;"); + + QString textColor; + if (transparency > 50) { + textColor = " color: white;"; + } + bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold;" + textColor); layout->addWidget(iconLabel); layout->addWidget(bannerLabel); From d8e3807ec50be2b384b2c07a552f9efd315b2bb1 Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 27 Mar 2026 18:11:56 +0100 Subject: [PATCH 17/60] Dependabot: Enable git submodules tracking (#6727) * Enable gitsubmodules * update comment --- .github/dependabot.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1e278a418..fc25af67f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,19 +2,18 @@ version: 2 updates: - # # Enable version updates for git submodules - # Not yet possible to bump only on tags or releases, see: + # Enable version updates for git submodules + # If SemVer is used, updates will happen to new releases only (not HEAD) # https://github.com/dependabot/dependabot-core/issues/1639 # https://github.com/dependabot/dependabot-core/issues/2192 - # Alternative: Action that updates submodule and can be manually run on demand (workflow_dispatch) - # - package-ecosystem: "gitsubmodule" - # # Look for `.gitmodules` in the `root` directory - # directory: "/" - # # Check for updates once a month - # schedule: - # interval: "monthly" - # # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted) - # open-pull-requests-limit: 1 + - package-ecosystem: "gitsubmodule" + # Look for `.gitmodules` in the `root` directory + directory: "/" + # Check for updates once a month + schedule: + interval: "monthly" + # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted) + open-pull-requests-limit: 2 # # Enable version updates for Docker # Not yet possible to bump from one LTS version to the next and skip others, see: From 34a5b8b9ce95fce726301819638f52d1781b8088 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:13:25 -0700 Subject: [PATCH 18/60] [SettingsManager] Make setting getters const (#6748) * [SettingsManager] Make setting getters const * remove hashGameType from header --- .../interface_card_set_priority_controller.h | 6 +-- .../noop_card_set_priority_controller.h | 6 +-- .../settings/card_database_settings.cpp | 6 +-- .../settings/card_database_settings.h | 6 +-- .../settings/card_override_settings.cpp | 2 +- .../settings/card_override_settings.h | 2 +- .../libcockatrice/settings/debug_settings.cpp | 8 ++-- .../libcockatrice/settings/debug_settings.h | 8 ++-- .../settings/download_settings.cpp | 2 +- .../settings/download_settings.h | 2 +- .../settings/game_filters_settings.cpp | 38 +++++++++---------- .../settings/game_filters_settings.h | 36 +++++++++--------- .../settings/layouts_settings.cpp | 26 ++++++------- .../libcockatrice/settings/layouts_settings.h | 26 ++++++------- .../settings/message_settings.cpp | 4 +- .../libcockatrice/settings/message_settings.h | 4 +- .../settings/recents_settings.cpp | 4 +- .../libcockatrice/settings/recents_settings.h | 4 +- .../settings/servers_settings.cpp | 26 ++++++------- .../libcockatrice/settings/servers_settings.h | 26 ++++++------- .../settings/settings_manager.cpp | 4 +- .../libcockatrice/settings/settings_manager.h | 4 +- 22 files changed, 124 insertions(+), 126 deletions(-) diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h index 46a5897f7..b8fbbc74a 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h @@ -12,9 +12,9 @@ public: virtual void setEnabled(QString shortName, bool enabled) = 0; virtual void setIsKnown(QString shortName, bool isknown) = 0; - virtual unsigned int getSortKey(QString shortName) = 0; - virtual bool isEnabled(QString shortName) = 0; - virtual bool isKnown(QString shortName) = 0; + virtual unsigned int getSortKey(QString shortName) const = 0; + virtual bool isEnabled(QString shortName) const = 0; + virtual bool isKnown(QString shortName) const = 0; }; #endif // COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h index 949ab5a91..e5027648c 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h @@ -16,15 +16,15 @@ public: { } - unsigned int getSortKey(QString /* shortName */) override + unsigned int getSortKey(QString /* shortName */) const override { return 0; } - bool isEnabled(QString /* shortName */) override + bool isEnabled(QString /* shortName */) const override { return true; } - bool isKnown(QString /* shortName */) override + bool isKnown(QString /* shortName */) const override { return true; } diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp b/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp index 79738f1cd..26a91a4dd 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp @@ -20,17 +20,17 @@ void CardDatabaseSettings::setIsKnown(QString shortName, bool isknown) setValue(isknown, "isknown", "sets", std::move(shortName)); } -unsigned int CardDatabaseSettings::getSortKey(QString shortName) +unsigned int CardDatabaseSettings::getSortKey(QString shortName) const { return getValue("sortkey", "sets", std::move(shortName)).toUInt(); } -bool CardDatabaseSettings::isEnabled(QString shortName) +bool CardDatabaseSettings::isEnabled(QString shortName) const { return getValue("enabled", "sets", std::move(shortName)).toBool(); } -bool CardDatabaseSettings::isKnown(QString shortName) +bool CardDatabaseSettings::isKnown(QString shortName) const { return getValue("isknown", "sets", std::move(shortName)).toBool(); } diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h index 9a176a99b..bb946ea80 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h @@ -22,9 +22,9 @@ public: void setEnabled(QString shortName, bool enabled) override; void setIsKnown(QString shortName, bool isknown) override; - unsigned int getSortKey(QString shortName) override; - bool isEnabled(QString shortName) override; - bool isKnown(QString shortName) override; + unsigned int getSortKey(QString shortName) const override; + bool isEnabled(QString shortName) const override; + bool isKnown(QString shortName) const override; private: explicit CardDatabaseSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp b/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp index 894358be6..a61a4693b 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp @@ -15,7 +15,7 @@ void CardOverrideSettings::deleteCardPreferenceOverride(const QString &cardName) deleteValue(cardName); } -QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName) +QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName) const { return getValue(cardName).toString(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/card_override_settings.h b/libcockatrice_settings/libcockatrice/settings/card_override_settings.h index d5ee0287b..3d9db4e65 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_override_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/card_override_settings.h @@ -22,7 +22,7 @@ public: void deleteCardPreferenceOverride(const QString &cardName); - QString getCardPreferenceOverride(const QString &cardName); + QString getCardPreferenceOverride(const QString &cardName) const; private: explicit CardOverrideSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp b/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp index 084696dc1..5bf6eca30 100644 --- a/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp @@ -11,22 +11,22 @@ DebugSettings::DebugSettings(const QString &settingPath, QObject *parent) } } -bool DebugSettings::getShowCardId() +bool DebugSettings::getShowCardId() const { return getValue("showCardId").toBool(); } -bool DebugSettings::getLocalGameOnStartup() +bool DebugSettings::getLocalGameOnStartup() const { return getValue("onStartup", "localgame").toBool(); } -int DebugSettings::getLocalGamePlayerCount() +int DebugSettings::getLocalGamePlayerCount() const { return getValue("playerCount", "localgame").toInt(); } -QString DebugSettings::getDeckPathForPlayer(const QString &playerName) +QString DebugSettings::getDeckPathForPlayer(const QString &playerName) const { return getValue(playerName, "localgame", "deck").toString(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/debug_settings.h b/libcockatrice_settings/libcockatrice/settings/debug_settings.h index 2087b16b3..30cdd5fa5 100644 --- a/libcockatrice_settings/libcockatrice/settings/debug_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/debug_settings.h @@ -17,12 +17,12 @@ class DebugSettings : public SettingsManager DebugSettings(const DebugSettings & /*other*/); public: - bool getShowCardId(); + bool getShowCardId() const; - bool getLocalGameOnStartup(); - int getLocalGamePlayerCount(); + bool getLocalGameOnStartup() const; + int getLocalGamePlayerCount() const; - QString getDeckPathForPlayer(const QString &playerName); + QString getDeckPathForPlayer(const QString &playerName) const; }; #endif // DEBUG_SETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.cpp b/libcockatrice_settings/libcockatrice/settings/download_settings.cpp index ad4b81ec5..66525a598 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.cpp @@ -18,7 +18,7 @@ void DownloadSettings::setDownloadUrls(const QStringList &downloadURLs) setValue(QVariant::fromValue(downloadURLs), "urls"); } -QStringList DownloadSettings::getAllURLs() +QStringList DownloadSettings::getAllURLs() const { return getValue("urls").toStringList(); } diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.h b/libcockatrice_settings/libcockatrice/settings/download_settings.h index ed3634ea1..b7442301e 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.h @@ -19,7 +19,7 @@ class DownloadSettings : public SettingsManager public: explicit DownloadSettings(const QString &, QObject *); - QStringList getAllURLs(); + QStringList getAllURLs() const; void setDownloadUrls(const QStringList &downloadURLs); void resetToDefaultURLs(); }; diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp index e5db3010d..4f5bf52ee 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp @@ -8,11 +8,11 @@ GameFiltersSettings::GameFiltersSettings(const QString &settingPath, QObject *pa { } -/* +/** * The game type might contain special characters, so to use it in * QSettings we just hash it. */ -QString GameFiltersSettings::hashGameType(const QString &gameType) const +static QString hashGameType(const QString &gameType) { return QCryptographicHash::hash(gameType.toUtf8(), QCryptographicHash::Md5).toHex(); } @@ -22,7 +22,7 @@ void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide) setValue(hide, "hide_buddies_only_games"); } -bool GameFiltersSettings::isHideBuddiesOnlyGames() +bool GameFiltersSettings::isHideBuddiesOnlyGames() const { QVariant previous = getValue("hide_buddies_only_games"); return previous == QVariant() ? false : previous.toBool(); @@ -33,7 +33,7 @@ void GameFiltersSettings::setHideFullGames(bool hide) setValue(hide, "hide_full_games"); } -bool GameFiltersSettings::isHideFullGames() +bool GameFiltersSettings::isHideFullGames() const { QVariant previous = getValue("hide_full_games"); return previous == QVariant() ? false : previous.toBool(); @@ -44,7 +44,7 @@ void GameFiltersSettings::setHideGamesThatStarted(bool hide) setValue(hide, "hide_games_that_started"); } -bool GameFiltersSettings::isHideGamesThatStarted() +bool GameFiltersSettings::isHideGamesThatStarted() const { QVariant previous = getValue("hide_games_that_started"); return previous == QVariant() ? false : previous.toBool(); @@ -55,7 +55,7 @@ void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) setValue(hide, "hide_password_protected_games"); } -bool GameFiltersSettings::isHidePasswordProtectedGames() +bool GameFiltersSettings::isHidePasswordProtectedGames() const { QVariant previous = getValue("hide_password_protected_games"); return previous == QVariant() ? false : previous.toBool(); @@ -66,7 +66,7 @@ void GameFiltersSettings::setHideIgnoredUserGames(bool hide) setValue(hide, "hide_ignored_user_games"); } -bool GameFiltersSettings::isHideIgnoredUserGames() +bool GameFiltersSettings::isHideIgnoredUserGames() const { QVariant previous = getValue("hide_ignored_user_games"); return previous == QVariant() ? true : previous.toBool(); @@ -77,7 +77,7 @@ void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide) setValue(hide, "hide_not_buddy_created_games"); } -bool GameFiltersSettings::isHideNotBuddyCreatedGames() +bool GameFiltersSettings::isHideNotBuddyCreatedGames() const { QVariant previous = getValue("hide_not_buddy_created_games"); return previous == QVariant() ? false : previous.toBool(); @@ -88,7 +88,7 @@ void GameFiltersSettings::setHideOpenDecklistGames(bool hide) setValue(hide, "hide_open_decklist_games"); } -bool GameFiltersSettings::isHideOpenDecklistGames() +bool GameFiltersSettings::isHideOpenDecklistGames() const { QVariant previous = getValue("hide_open_decklist_games"); return previous == QVariant() ? false : previous.toBool(); @@ -99,7 +99,7 @@ void GameFiltersSettings::setGameNameFilter(QString gameName) setValue(gameName, "game_name_filter"); } -QString GameFiltersSettings::getGameNameFilter() +QString GameFiltersSettings::getGameNameFilter() const { return getValue("game_name_filter").toString(); } @@ -109,7 +109,7 @@ void GameFiltersSettings::setCreatorNameFilters(QStringList creatorName) setValue(creatorName, "creator_name_filter"); } -QStringList GameFiltersSettings::getCreatorNameFilters() +QStringList GameFiltersSettings::getCreatorNameFilters() const { return getValue("creator_name_filter").toStringList(); } @@ -119,7 +119,7 @@ void GameFiltersSettings::setMinPlayers(int min) setValue(min, "min_players"); } -int GameFiltersSettings::getMinPlayers() +int GameFiltersSettings::getMinPlayers() const { QVariant previous = getValue("min_players"); return previous == QVariant() ? 1 : previous.toInt(); @@ -130,7 +130,7 @@ void GameFiltersSettings::setMaxPlayers(int max) setValue(max, "max_players"); } -int GameFiltersSettings::getMaxPlayers() +int GameFiltersSettings::getMaxPlayers() const { QVariant previous = getValue("max_players"); return previous == QVariant() ? 99 : previous.toInt(); @@ -141,7 +141,7 @@ void GameFiltersSettings::setMaxGameAge(const QTime &maxGameAge) setValue(maxGameAge, "max_game_age_time"); } -QTime GameFiltersSettings::getMaxGameAge() +QTime GameFiltersSettings::getMaxGameAge() const { QVariant previous = getValue("max_game_age_time"); return previous.toTime(); @@ -157,7 +157,7 @@ void GameFiltersSettings::setGameHashedTypeEnabled(QString gametypeHASHED, bool setValue(enabled, gametypeHASHED); } -bool GameFiltersSettings::isGameTypeEnabled(QString gametype) +bool GameFiltersSettings::isGameTypeEnabled(QString gametype) const { QVariant previous = getValue("game_type/" + hashGameType(gametype)); return previous == QVariant() ? false : previous.toBool(); @@ -168,7 +168,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanWatch(bool show) setValue(show, "show_only_if_spectators_can_watch"); } -bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch() +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch() const { QVariant previous = getValue("show_only_if_spectators_can_watch"); return previous == QVariant() ? false : previous.toBool(); @@ -179,7 +179,7 @@ void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show) setValue(show, "show_spectator_password_protected"); } -bool GameFiltersSettings::isShowSpectatorPasswordProtected() +bool GameFiltersSettings::isShowSpectatorPasswordProtected() const { QVariant previous = getValue("show_spectator_password_protected"); return previous == QVariant() ? false : previous.toBool(); @@ -190,7 +190,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show) setValue(show, "show_only_if_spectators_can_chat"); } -bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() const { QVariant previous = getValue("show_only_if_spectators_can_chat"); return previous == QVariant() ? false : previous.toBool(); @@ -201,7 +201,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show) setValue(show, "show_only_if_spectators_can_see_hands"); } -bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() const { QVariant previous = getValue("show_only_if_spectators_can_see_hands"); return previous == QVariant() ? false : previous.toBool(); diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h index 45e9b7441..c0e60551a 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h @@ -16,23 +16,23 @@ class GameFiltersSettings : public SettingsManager friend class SettingsCache; public: - bool isHideBuddiesOnlyGames(); - bool isHideFullGames(); - bool isHideGamesThatStarted(); - bool isHidePasswordProtectedGames(); - bool isHideIgnoredUserGames(); - bool isHideNotBuddyCreatedGames(); - bool isHideOpenDecklistGames(); - QString getGameNameFilter(); - QStringList getCreatorNameFilters(); - int getMinPlayers(); - int getMaxPlayers(); - QTime getMaxGameAge(); - bool isGameTypeEnabled(QString gametype); - bool isShowOnlyIfSpectatorsCanWatch(); - bool isShowSpectatorPasswordProtected(); - bool isShowOnlyIfSpectatorsCanChat(); - bool isShowOnlyIfSpectatorsCanSeeHands(); + bool isHideBuddiesOnlyGames() const; + bool isHideFullGames() const; + bool isHideGamesThatStarted() const; + bool isHidePasswordProtectedGames() const; + bool isHideIgnoredUserGames() const; + bool isHideNotBuddyCreatedGames() const; + bool isHideOpenDecklistGames() const; + QString getGameNameFilter() const; + QStringList getCreatorNameFilters() const; + int getMinPlayers() const; + int getMaxPlayers() const; + QTime getMaxGameAge() const; + bool isGameTypeEnabled(QString gametype) const; + bool isShowOnlyIfSpectatorsCanWatch() const; + bool isShowSpectatorPasswordProtected() const; + bool isShowOnlyIfSpectatorsCanChat() const; + bool isShowOnlyIfSpectatorsCanSeeHands() const; void setHideBuddiesOnlyGames(bool hide); void setHideIgnoredUserGames(bool hide); @@ -56,8 +56,6 @@ public: private: explicit GameFiltersSettings(const QString &settingPath, QObject *parent = nullptr); GameFiltersSettings(const GameFiltersSettings & /*other*/); - - [[nodiscard]] QString hashGameType(const QString &gameType) const; }; #endif // GAMEFILTERSSETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index f7704cbc7..e914dc2d8 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -23,12 +23,12 @@ void LayoutsSettings::setMainWindowGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_MAIN_WINDOW); } -QByteArray LayoutsSettings::getMainWindowGeometry() +QByteArray LayoutsSettings::getMainWindowGeometry() const { return getValue(GEOMETRY_PROP, GROUP_MAIN_WINDOW).toByteArray(); } -QByteArray LayoutsSettings::getDeckEditorLayoutState() +QByteArray LayoutsSettings::getDeckEditorLayoutState() const { return getValue(STATE_PROP, GROUP_DECK_EDITOR).toByteArray(); } @@ -38,7 +38,7 @@ void LayoutsSettings::setDeckEditorLayoutState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_DECK_EDITOR); } -QByteArray LayoutsSettings::getDeckEditorGeometry() +QByteArray LayoutsSettings::getDeckEditorGeometry() const { return getValue(GEOMETRY_PROP, GROUP_DECK_EDITOR).toByteArray(); } @@ -48,7 +48,7 @@ void LayoutsSettings::setDeckEditorGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_DECK_EDITOR); } -QByteArray LayoutsSettings::getVisualDeckEditorLayoutState() +QByteArray LayoutsSettings::getVisualDeckEditorLayoutState() const { return getValue(STATE_PROP, GROUP_VISUAL_DECK_EDITOR).toByteArray(); } @@ -58,7 +58,7 @@ void LayoutsSettings::setVisualDeckEditorLayoutState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_VISUAL_DECK_EDITOR); } -QByteArray LayoutsSettings::getVisualDeckEditorGeometry() +QByteArray LayoutsSettings::getVisualDeckEditorGeometry() const { return getValue(GEOMETRY_PROP, GROUP_VISUAL_DECK_EDITOR).toByteArray(); } @@ -68,7 +68,7 @@ void LayoutsSettings::setVisualDeckEditorGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_VISUAL_DECK_EDITOR); } -QByteArray LayoutsSettings::getDeckEditorDbHeaderState() +QByteArray LayoutsSettings::getDeckEditorDbHeaderState() const { return getValue(STATE_PROP, GROUP_DECK_EDITOR_DB, "header").toByteArray(); } @@ -78,7 +78,7 @@ void LayoutsSettings::setDeckEditorDbHeaderState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_DECK_EDITOR_DB, "header"); } -QByteArray LayoutsSettings::getSetsDialogHeaderState() +QByteArray LayoutsSettings::getSetsDialogHeaderState() const { return getValue(STATE_PROP, GROUP_SETS_DIALOG, "header").toByteArray(); } @@ -93,7 +93,7 @@ void LayoutsSettings::setSetsDialogGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_SETS_DIALOG); } -QByteArray LayoutsSettings::getSetsDialogGeometry() +QByteArray LayoutsSettings::getSetsDialogGeometry() const { return getValue(GEOMETRY_PROP, GROUP_SETS_DIALOG).toByteArray(); } @@ -103,7 +103,7 @@ void LayoutsSettings::setTokenDialogGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_TOKEN_DIALOG); } -QByteArray LayoutsSettings::getTokenDialogGeometry() +QByteArray LayoutsSettings::getTokenDialogGeometry() const { return getValue(GEOMETRY_PROP, GROUP_TOKEN_DIALOG).toByteArray(); } @@ -118,12 +118,12 @@ void LayoutsSettings::setGamePlayAreaState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_GAME_PLAY_AREA); } -QByteArray LayoutsSettings::getGamePlayAreaLayoutState() +QByteArray LayoutsSettings::getGamePlayAreaLayoutState() const { return getValue(STATE_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } -QByteArray LayoutsSettings::getGamePlayAreaGeometry() +QByteArray LayoutsSettings::getGamePlayAreaGeometry() const { return getValue(GEOMETRY_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } @@ -138,12 +138,12 @@ void LayoutsSettings::setReplayPlayAreaState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_REPLAY_PLAY_AREA); } -QByteArray LayoutsSettings::getReplayPlayAreaLayoutState() +QByteArray LayoutsSettings::getReplayPlayAreaLayoutState() const { return getValue(STATE_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } -QByteArray LayoutsSettings::getReplayPlayAreaGeometry() +QByteArray LayoutsSettings::getReplayPlayAreaGeometry() const { return getValue(GEOMETRY_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index cab9a456e..5353ce15a 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -36,24 +36,24 @@ public: void setReplayPlayAreaGeometry(const QByteArray &value); void setReplayPlayAreaState(const QByteArray &value); - QByteArray getMainWindowGeometry(); + QByteArray getMainWindowGeometry() const; - QByteArray getDeckEditorLayoutState(); - QByteArray getDeckEditorGeometry(); + QByteArray getDeckEditorLayoutState() const; + QByteArray getDeckEditorGeometry() const; - QByteArray getVisualDeckEditorLayoutState(); - QByteArray getVisualDeckEditorGeometry(); + QByteArray getVisualDeckEditorLayoutState() const; + QByteArray getVisualDeckEditorGeometry() const; - QByteArray getDeckEditorDbHeaderState(); - QByteArray getSetsDialogHeaderState(); - QByteArray getSetsDialogGeometry(); - QByteArray getTokenDialogGeometry(); + QByteArray getDeckEditorDbHeaderState() const; + QByteArray getSetsDialogHeaderState() const; + QByteArray getSetsDialogGeometry() const; + QByteArray getTokenDialogGeometry() const; - QByteArray getGamePlayAreaLayoutState(); - QByteArray getGamePlayAreaGeometry(); + QByteArray getGamePlayAreaLayoutState() const; + QByteArray getGamePlayAreaGeometry() const; - QByteArray getReplayPlayAreaLayoutState(); - QByteArray getReplayPlayAreaGeometry(); + QByteArray getReplayPlayAreaLayoutState() const; + QByteArray getReplayPlayAreaGeometry() const; signals: public slots: diff --git a/libcockatrice_settings/libcockatrice/settings/message_settings.cpp b/libcockatrice_settings/libcockatrice/settings/message_settings.cpp index 761c94484..50da39df6 100644 --- a/libcockatrice_settings/libcockatrice/settings/message_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/message_settings.cpp @@ -5,12 +5,12 @@ MessageSettings::MessageSettings(const QString &settingPath, QObject *parent) { } -QString MessageSettings::getMessageAt(int index) +QString MessageSettings::getMessageAt(int index) const { return getValue(QString("msg%1").arg(index)).toString(); } -int MessageSettings::getCount() +int MessageSettings::getCount() const { return getValue("count").toInt(); } diff --git a/libcockatrice_settings/libcockatrice/settings/message_settings.h b/libcockatrice_settings/libcockatrice/settings/message_settings.h index 265c455e1..ec70027af 100644 --- a/libcockatrice_settings/libcockatrice/settings/message_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/message_settings.h @@ -15,8 +15,8 @@ class MessageSettings : public SettingsManager friend class SettingsCache; public: - int getCount(); - QString getMessageAt(int index); + int getCount() const; + QString getMessageAt(int index) const; void setCount(int count); void setMessageAt(int index, QString message); diff --git a/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp b/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp index e64dd7494..76bc4069e 100644 --- a/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp @@ -7,7 +7,7 @@ RecentsSettings::RecentsSettings(const QString &settingPath, QObject *parent) { } -QStringList RecentsSettings::getRecentlyOpenedDeckPaths() +QStringList RecentsSettings::getRecentlyOpenedDeckPaths() const { return getValue("deckpaths").toStringList(); } @@ -31,7 +31,7 @@ void RecentsSettings::updateRecentlyOpenedDeckPaths(const QString &deckPath) emit recentlyOpenedDeckPathsChanged(); } -QString RecentsSettings::getLatestDeckDirPath() +QString RecentsSettings::getLatestDeckDirPath() const { return getValue("latestDeckDir", "dirs").toString(); } diff --git a/libcockatrice_settings/libcockatrice/settings/recents_settings.h b/libcockatrice_settings/libcockatrice/settings/recents_settings.h index 23c8f1a9f..3aebff334 100644 --- a/libcockatrice_settings/libcockatrice/settings/recents_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/recents_settings.h @@ -18,11 +18,11 @@ class RecentsSettings : public SettingsManager RecentsSettings(const RecentsSettings & /*other*/); public: - QStringList getRecentlyOpenedDeckPaths(); + QStringList getRecentlyOpenedDeckPaths() const; void clearRecentlyOpenedDeckPaths(); void updateRecentlyOpenedDeckPaths(const QString &deckPath); - QString getLatestDeckDirPath(); + QString getLatestDeckDirPath() const; void setLatestDeckDirPath(const QString &dirPath); signals: diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp index 5f69f47c9..0140182be 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp @@ -13,7 +13,7 @@ void ServersSettings::setPreviousHostLogin(int previous) setValue(previous, "previoushostlogin"); } -int ServersSettings::getPreviousHostLogin() +int ServersSettings::getPreviousHostLogin() const { QVariant previous = getValue("previoushostlogin"); return previous == QVariant() ? 1 : previous.toInt(); @@ -24,7 +24,7 @@ void ServersSettings::setPreviousHostList(QStringList list) setValue(list, "previoushosts"); } -QStringList ServersSettings::getPreviousHostList() +QStringList ServersSettings::getPreviousHostList() const { return getValue("previoushosts").toStringList(); } @@ -48,13 +48,13 @@ QString ServersSettings::getSite(QString defaultSite) return site == QVariant() ? std::move(defaultSite) : site.toString(); } -QString ServersSettings::getPrevioushostName() +QString ServersSettings::getPrevioushostName() const { QVariant value = getValue("previoushostName"); return value == QVariant() ? "Rooster Ranges" : value.toString(); } -int ServersSettings::getPrevioushostindex(const QString &saveName) +int ServersSettings::getPrevioushostindex(const QString &saveName) const { int size = getValue("totalServers", "server", "server_details").toInt(); @@ -65,14 +65,14 @@ int ServersSettings::getPrevioushostindex(const QString &saveName) return -1; } -QString ServersSettings::getHostname(QString defaultHost) +QString ServersSettings::getHostname(QString defaultHost) const { int index = getPrevioushostindex(getPrevioushostName()); QVariant hostname = getValue(QString("server%1").arg(index), "server", "server_details"); return hostname == QVariant() ? std::move(defaultHost) : hostname.toString(); } -QString ServersSettings::getPort(QString defaultPort) +QString ServersSettings::getPort(QString defaultPort) const { int index = getPrevioushostindex(getPrevioushostName()); QVariant port = getValue(QString("port%1").arg(index), "server", "server_details"); @@ -80,7 +80,7 @@ QString ServersSettings::getPort(QString defaultPort) return port == QVariant() ? std::move(defaultPort) : port.toString(); } -QString ServersSettings::getPlayerName(QString defaultName) +QString ServersSettings::getPlayerName(QString defaultName) const { int index = getPrevioushostindex(getPrevioushostName()); QVariant name = getValue(QString("username%1").arg(index), "server", "server_details"); @@ -98,7 +98,7 @@ QString ServersSettings::getPassword() return QString(); } -bool ServersSettings::getSavePassword() +bool ServersSettings::getSavePassword() const { int index = getPrevioushostindex(getPrevioushostName()); bool save = getValue(QString("savePassword%1").arg(index), "server", "server_details").toBool(); @@ -110,7 +110,7 @@ void ServersSettings::setAutoConnect(int autoconnect) setValue(autoconnect, "auto_connect"); } -int ServersSettings::getAutoConnect() +int ServersSettings::getAutoConnect() const { QVariant autoconnect = getValue("auto_connect"); return autoconnect == QVariant() ? 0 : autoconnect.toInt(); @@ -121,7 +121,7 @@ void ServersSettings::setFPHostName(QString hostname) setValue(hostname, "fphostname"); } -QString ServersSettings::getFPHostname(QString defaultHost) +QString ServersSettings::getFPHostname(QString defaultHost) const { QVariant hostname = getValue("fphostname"); return hostname == QVariant() ? std::move(defaultHost) : hostname.toString(); @@ -132,7 +132,7 @@ void ServersSettings::setFPPort(QString port) setValue(port, "fpport"); } -QString ServersSettings::getFPPort(QString defaultPort) +QString ServersSettings::getFPPort(QString defaultPort) const { QVariant port = getValue("fpport"); return port == QVariant() ? std::move(defaultPort) : port.toString(); @@ -143,7 +143,7 @@ void ServersSettings::setFPPlayerName(QString playerName) setValue(playerName, "fpplayername"); } -QString ServersSettings::getFPPlayerName(QString defaultName) +QString ServersSettings::getFPPlayerName(QString defaultName) const { QVariant name = getValue("fpplayername"); return name == QVariant() ? std::move(defaultName) : name.toString(); @@ -154,7 +154,7 @@ void ServersSettings::setClearDebugLogStatus(bool abIsChecked) setValue(abIsChecked, "save_debug_log"); } -bool ServersSettings::getClearDebugLogStatus(bool abDefaultValue) +bool ServersSettings::getClearDebugLogStatus(bool abDefaultValue) const { QVariant cbFlushLog = getValue("save_debug_log"); return cbFlushLog == QVariant() ? abDefaultValue : cbFlushLog.toBool(); diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.h b/libcockatrice_settings/libcockatrice/settings/servers_settings.h index 4d92c4647..22603a356 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.h @@ -22,21 +22,21 @@ class ServersSettings : public SettingsManager friend class SettingsCache; public: - int getPreviousHostLogin(); - int getPrevioushostindex(const QString &); - QStringList getPreviousHostList(); - QString getPrevioushostName(); - QString getHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST); - QString getPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT); - QString getPlayerName(QString defaultName = ""); - QString getFPHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST); - QString getFPPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT); - QString getFPPlayerName(QString defaultName = ""); + int getPreviousHostLogin() const; + int getPrevioushostindex(const QString &) const; + QStringList getPreviousHostList() const; + QString getPrevioushostName() const; + QString getHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST) const; + QString getPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT) const; + QString getPlayerName(QString defaultName = "") const; + QString getFPHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST) const; + QString getFPPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT) const; + QString getFPPlayerName(QString defaultName = "") const; QString getPassword(); QString getSaveName(QString defaultname = ""); QString getSite(QString defaultName = ""); - bool getSavePassword(); - int getAutoConnect(); + bool getSavePassword() const; + int getAutoConnect() const; void setPreviousHostLogin(int previous); void setPrevioushostName(const QString &); @@ -67,7 +67,7 @@ public: QString port = QString(), QString site = QString()); void setClearDebugLogStatus(bool abIsChecked); - bool getClearDebugLogStatus(bool abDefaultValue); + bool getClearDebugLogStatus(bool abDefaultValue) const; private: explicit ServersSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp index 3f3deb638..2d4f1c441 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp @@ -108,7 +108,7 @@ void SettingsManager::deleteValue(const QString &name, const QString &group, con } } -QVariant SettingsManager::getValue(const QString &name) +QVariant SettingsManager::getValue(const QString &name) const { auto settings = getSettings(); @@ -133,7 +133,7 @@ QVariant SettingsManager::getValue(const QString &name) return value; } -QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup) +QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup) const { auto settings = getSettings(); diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index 18abb8dbf..ad828f089 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -19,8 +19,8 @@ public: const QString &defaultGroup = QString(), const QString &defaultSubGroup = QString(), QObject *parent = nullptr); - QVariant getValue(const QString &name); - QVariant getValue(const QString &name, const QString &group, const QString &subGroup = QString()); + QVariant getValue(const QString &name) const; + QVariant getValue(const QString &name, const QString &group, const QString &subGroup = QString()) const; void sync(); protected: From 7caa88bc58b649b2536d83fed35fdcf2743dbdb6 Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:37:30 -0300 Subject: [PATCH 19/60] fix: solve deprecated literal operator error by updating peglib (#6745) * fix: solve deprecated literal operator error * update peglib https://github.com/yhirose/cpp-peglib/commit/dcabc63cf4b966eb6f33abd571ae4cda6bf707f0 --- .../libcockatrice/utility/peglib.h | 8083 +++++++++-------- 1 file changed, 4452 insertions(+), 3631 deletions(-) diff --git a/libcockatrice_utility/libcockatrice/utility/peglib.h b/libcockatrice_utility/libcockatrice/utility/peglib.h index 6a5b87b2d..3ae6040c4 100644 --- a/libcockatrice_utility/libcockatrice/utility/peglib.h +++ b/libcockatrice_utility/libcockatrice/utility/peglib.h @@ -1,4 +1,4 @@ -// +// // peglib.h // // Copyright (c) 2022 Yuji Hirose. All rights reserved. @@ -17,6 +17,7 @@ #include #include +#include #include #include #if __has_include() @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -51,28 +53,28 @@ namespace peg { // "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". template struct scope_exit { - explicit scope_exit(EF &&f) - : exit_function(std::move(f)), execute_on_destruction{true} {} + explicit scope_exit(EF &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} - scope_exit(scope_exit &&rhs) - : exit_function(std::move(rhs.exit_function)), - execute_on_destruction{rhs.execute_on_destruction} { - rhs.release(); - } + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } - ~scope_exit() { - if (execute_on_destruction) { this->exit_function(); } - } + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } - void release() { this->execute_on_destruction = false; } + void release() { this->execute_on_destruction = false; } private: - scope_exit(const scope_exit &) = delete; - void operator=(const scope_exit &) = delete; - scope_exit &operator=(scope_exit &&) = delete; + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; - EF exit_function; - bool execute_on_destruction; + EF exit_function; + bool execute_on_destruction; }; /*----------------------------------------------------------------------------- @@ -80,130 +82,136 @@ private: *---------------------------------------------------------------------------*/ inline size_t codepoint_length(const char *s8, size_t l) { - if (l) { - auto b = static_cast(s8[0]); - if ((b & 0x80) == 0) { - return 1; - } else if ((b & 0xE0) == 0xC0 && l >= 2) { - return 2; - } else if ((b & 0xF0) == 0xE0 && l >= 3) { - return 3; - } else if ((b & 0xF8) == 0xF0 && l >= 4) { - return 4; - } + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + return 1; + } else if ((b & 0xE0) == 0xC0 && l >= 2) { + return 2; + } else if ((b & 0xF0) == 0xE0 && l >= 3) { + return 3; + } else if ((b & 0xF8) == 0xF0 && l >= 4) { + return 4; } - return 0; + } + return 0; } inline size_t codepoint_count(const char *s8, size_t l) { - size_t count = 0; - for (size_t i = 0; i < l; i += codepoint_length(s8 + i, l - i)) { - count++; + size_t count = 0; + for (size_t i = 0; i < l;) { + auto len = codepoint_length(s8 + i, l - i); + if (len == 0) { + // Invalid UTF-8 byte, treat as single byte to avoid infinite loop + len = 1; } - return count; + i += len; + count++; + } + return count; } inline size_t encode_codepoint(char32_t cp, char *buff) { - if (cp < 0x0080) { - buff[0] = static_cast(cp & 0x7F); - return 1; - } else if (cp < 0x0800) { - buff[0] = static_cast(0xC0 | ((cp >> 6) & 0x1F)); - buff[1] = static_cast(0x80 | (cp & 0x3F)); - return 2; - } else if (cp < 0xD800) { - buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); - buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[2] = static_cast(0x80 | (cp & 0x3F)); - return 3; - } else if (cp < 0xE000) { - // D800 - DFFF is invalid... - return 0; - } else if (cp < 0x10000) { - buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); - buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[2] = static_cast(0x80 | (cp & 0x3F)); - return 3; - } else if (cp < 0x110000) { - buff[0] = static_cast(0xF0 | ((cp >> 18) & 0x7)); - buff[1] = static_cast(0x80 | ((cp >> 12) & 0x3F)); - buff[2] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[3] = static_cast(0x80 | (cp & 0x3F)); - return 4; - } + if (cp < 0x0080) { + buff[0] = static_cast(cp & 0x7F); + return 1; + } else if (cp < 0x0800) { + buff[0] = static_cast(0xC0 | ((cp >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (cp & 0x3F)); + return 2; + } else if (cp < 0xD800) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0xE000) { + // D800 - DFFF is invalid... return 0; + } else if (cp < 0x10000) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0x110000) { + buff[0] = static_cast(0xF0 | ((cp >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((cp >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (cp & 0x3F)); + return 4; + } + return 0; } inline std::string encode_codepoint(char32_t cp) { - char buff[4]; - auto l = encode_codepoint(cp, buff); - return std::string(buff, l); + char buff[4]; + auto l = encode_codepoint(cp, buff); + return std::string(buff, l); } inline bool decode_codepoint(const char *s8, size_t l, size_t &bytes, char32_t &cp) { - if (l) { - auto b = static_cast(s8[0]); - if ((b & 0x80) == 0) { - bytes = 1; - cp = b; - return true; - } else if ((b & 0xE0) == 0xC0) { - if (l >= 2) { - bytes = 2; - cp = ((static_cast(s8[0] & 0x1F)) << 6) | - (static_cast(s8[1] & 0x3F)); - return true; - } - } else if ((b & 0xF0) == 0xE0) { - if (l >= 3) { - bytes = 3; - cp = ((static_cast(s8[0] & 0x0F)) << 12) | - ((static_cast(s8[1] & 0x3F)) << 6) | - (static_cast(s8[2] & 0x3F)); - return true; - } - } else if ((b & 0xF8) == 0xF0) { - if (l >= 4) { - bytes = 4; - cp = ((static_cast(s8[0] & 0x07)) << 18) | - ((static_cast(s8[1] & 0x3F)) << 12) | - ((static_cast(s8[2] & 0x3F)) << 6) | - (static_cast(s8[3] & 0x3F)); - return true; - } - } + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + bytes = 1; + cp = b; + return true; + } else if ((b & 0xE0) == 0xC0) { + if (l >= 2) { + bytes = 2; + cp = ((static_cast(s8[0] & 0x1F)) << 6) | + (static_cast(s8[1] & 0x3F)); + return true; + } + } else if ((b & 0xF0) == 0xE0) { + if (l >= 3) { + bytes = 3; + cp = ((static_cast(s8[0] & 0x0F)) << 12) | + ((static_cast(s8[1] & 0x3F)) << 6) | + (static_cast(s8[2] & 0x3F)); + return true; + } + } else if ((b & 0xF8) == 0xF0) { + if (l >= 4) { + bytes = 4; + cp = ((static_cast(s8[0] & 0x07)) << 18) | + ((static_cast(s8[1] & 0x3F)) << 12) | + ((static_cast(s8[2] & 0x3F)) << 6) | + (static_cast(s8[3] & 0x3F)); + return true; + } } - return false; + } + return false; } inline size_t decode_codepoint(const char *s8, size_t l, char32_t &cp) { - size_t bytes; - if (decode_codepoint(s8, l, bytes, cp)) { return bytes; } - return 0; + size_t bytes; + if (decode_codepoint(s8, l, bytes, cp)) { return bytes; } + return 0; } inline char32_t decode_codepoint(const char *s8, size_t l) { - char32_t cp = 0; - decode_codepoint(s8, l, cp); - return cp; + char32_t cp = 0; + decode_codepoint(s8, l, cp); + return cp; } inline std::u32string decode(const char *s8, size_t l) { - std::u32string out; - size_t i = 0; - while (i < l) { - auto beg = i++; - while (i < l && (s8[i] & 0xc0) == 0x80) { - i++; - } - out += decode_codepoint(&s8[beg], (i - beg)); + std::u32string out; + size_t i = 0; + while (i < l) { + auto beg = i++; + while (i < l && (s8[i] & 0xc0) == 0x80) { + i++; } - return out; + out += decode_codepoint(&s8[beg], (i - beg)); + } + return out; } template const char *u8(const T *s) { - return reinterpret_cast(s); + return reinterpret_cast(s); } /*----------------------------------------------------------------------------- @@ -211,23 +219,23 @@ template const char *u8(const T *s) { *---------------------------------------------------------------------------*/ inline std::string escape_characters(const char *s, size_t n) { - std::string str; - for (size_t i = 0; i < n; i++) { - auto c = s[i]; - switch (c) { - case '\f': str += "\\f"; break; - case '\n': str += "\\n"; break; - case '\r': str += "\\r"; break; - case '\t': str += "\\t"; break; - case '\v': str += "\\v"; break; - default: str += c; break; - } + std::string str; + for (size_t i = 0; i < n; i++) { + auto c = s[i]; + switch (c) { + case '\f': str += "\\f"; break; + case '\n': str += "\\n"; break; + case '\r': str += "\\r"; break; + case '\t': str += "\\t"; break; + case '\v': str += "\\v"; break; + default: str += c; break; } - return str; + } + return str; } inline std::string escape_characters(std::string_view sv) { - return escape_characters(sv.data(), sv.size()); + return escape_characters(sv.data(), sv.size()); } /*----------------------------------------------------------------------------- @@ -235,120 +243,121 @@ inline std::string escape_characters(std::string_view sv) { *---------------------------------------------------------------------------*/ inline bool is_hex(char c, int &v) { - if ('0' <= c && c <= '9') { - v = c - '0'; - return true; - } else if ('a' <= c && c <= 'f') { - v = c - 'a' + 10; - return true; - } else if ('A' <= c && c <= 'F') { - v = c - 'A' + 10; - return true; - } - return false; + if ('0' <= c && c <= '9') { + v = c - '0'; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } + return false; } inline bool is_digit(char c, int &v) { - if ('0' <= c && c <= '9') { - v = c - '0'; - return true; - } - return false; + if ('0' <= c && c <= '9') { + v = c - '0'; + return true; + } + return false; } inline std::pair parse_hex_number(const char *s, size_t n, size_t i) { - int ret = 0; - int val; - while (i < n && is_hex(s[i], val)) { - ret = static_cast(ret * 16 + val); - i++; - } - return std::pair(ret, i); + int ret = 0; + int val; + while (i < n && is_hex(s[i], val)) { + ret = static_cast(ret * 16 + val); + i++; + } + return std::pair(ret, i); } inline std::pair parse_octal_number(const char *s, size_t n, size_t i) { - int ret = 0; - int val; - while (i < n && is_digit(s[i], val)) { - ret = static_cast(ret * 8 + val); - i++; - } - return std::pair(ret, i); + int ret = 0; + int val; + while (i < n && is_digit(s[i], val)) { + ret = static_cast(ret * 8 + val); + i++; + } + return std::pair(ret, i); } inline std::string resolve_escape_sequence(const char *s, size_t n) { - std::string r; - r.reserve(n); + std::string r; + r.reserve(n); - size_t i = 0; - while (i < n) { - auto ch = s[i]; - if (ch == '\\') { - i++; - if (i == n) { throw std::runtime_error("Invalid escape sequence..."); } - switch (s[i]) { - case 'f': - r += '\f'; - i++; - break; - case 'n': - r += '\n'; - i++; - break; - case 'r': - r += '\r'; - i++; - break; - case 't': - r += '\t'; - i++; - break; - case 'v': - r += '\v'; - i++; - break; - case '\'': - r += '\''; - i++; - break; - case '"': - r += '"'; - i++; - break; - case '[': - r += '['; - i++; - break; - case ']': - r += ']'; - i++; - break; - case '\\': - r += '\\'; - i++; - break; - case 'x': - case 'u': { - char32_t cp; - std::tie(cp, i) = parse_hex_number(s, n, i + 1); - r += encode_codepoint(cp); - break; - } - default: { - char32_t cp; - std::tie(cp, i) = parse_octal_number(s, n, i); - r += encode_codepoint(cp); - break; - } - } - } else { - r += ch; - i++; - } + size_t i = 0; + while (i < n) { + auto ch = s[i]; + if (ch == '\\') { + i++; + assert(i < n); + + switch (s[i]) { + case 'f': + r += '\f'; + i++; + break; + case 'n': + r += '\n'; + i++; + break; + case 'r': + r += '\r'; + i++; + break; + case 't': + r += '\t'; + i++; + break; + case 'v': + r += '\v'; + i++; + break; + case '\'': + r += '\''; + i++; + break; + case '"': + r += '"'; + i++; + break; + case '[': + r += '['; + i++; + break; + case ']': + r += ']'; + i++; + break; + case '\\': + r += '\\'; + i++; + break; + case 'x': + case 'u': { + char32_t cp; + std::tie(cp, i) = parse_hex_number(s, n, i + 1); + r += encode_codepoint(cp); + break; + } + default: { + char32_t cp; + std::tie(cp, i) = parse_octal_number(s, n, i); + r += encode_codepoint(cp); + break; + } + } + } else { + r += ch; + i++; } - return r; + } + return r; } /*----------------------------------------------------------------------------- @@ -356,19 +365,26 @@ inline std::string resolve_escape_sequence(const char *s, size_t n) { *---------------------------------------------------------------------------*/ template T token_to_number_(std::string_view sv) { - T n = 0; + T n = 0; #if __has_include() - if constexpr (!std::is_floating_point::value) { - std::from_chars(sv.data(), sv.data() + sv.size(), n); + if constexpr (!std::is_floating_point::value) { + std::from_chars(sv.data(), sv.data() + sv.size(), n); #else - if constexpr (false) { + if constexpr (false) { #endif - } else { - auto s = std::string(sv); - std::istringstream ss(s); - ss >> n; - } - return n; + } else { + auto s = std::string(sv); + std::istringstream ss(s); + ss >> n; + } + return n; +} + +inline std::string to_lower(std::string s) { + for (auto &c : s) { + c = static_cast(std::tolower(static_cast(c))); + } + return s; } /*----------------------------------------------------------------------------- @@ -377,75 +393,75 @@ template T token_to_number_(std::string_view sv) { class Trie { public: - Trie(const std::vector &items, bool ignore_case) - : ignore_case_(ignore_case) { - size_t id = 0; - for (const auto &item : items) { - const auto &s = ignore_case ? to_lower(item) : item; - for (size_t len = 1; len <= item.size(); len++) { - auto last = len == item.size(); - std::string_view sv(s.data(), len); - auto it = dic_.find(sv); - if (it == dic_.end()) { - dic_.emplace(sv, Info{last, last, id}); - } else if (last) { - it->second.match = true; - } else { - it->second.done = false; - } - } - id++; + Trie(const std::vector &items, bool ignore_case) + : ignore_case_(ignore_case), items_count_(items.size()) { + size_t id = 0; + for (const auto &item : items) { + const auto &s = ignore_case ? to_lower(item) : item; + if (item.size() > max_len_) { max_len_ = item.size(); } + for (size_t len = 1; len <= item.size(); len++) { + auto last = len == item.size(); + std::string_view sv(s.data(), len); + auto it = dic_.find(sv); + if (it == dic_.end()) { + dic_.emplace(sv, Info{last, last, id}); + } else if (last) { + it->second.match = true; + } else { + it->second.done = false; } + } + id++; + } + } + + size_t match(const char *text, size_t text_len, size_t &id) const { + auto limit = std::min(text_len, max_len_); + std::string lower_text; + if (ignore_case_) { + lower_text = to_lower(std::string(text, limit)); + text = lower_text.data(); } - size_t match(const char *text, size_t text_len, size_t &id) const { - std::string lower_text; - if (ignore_case_) { - lower_text = to_lower(text); - text = lower_text.data(); + size_t match_len = 0; + auto done = false; + size_t len = 1; + while (!done && len <= limit) { + std::string_view sv(text, len); + auto it = dic_.find(sv); + if (it == dic_.end()) { + done = true; + } else { + if (it->second.match) { + match_len = len; + id = it->second.id; } - - size_t match_len = 0; - auto done = false; - size_t len = 1; - while (!done && len <= text_len) { - std::string_view sv(text, len); - auto it = dic_.find(sv); - if (it == dic_.end()) { - done = true; - } else { - if (it->second.match) { - match_len = len; - id = it->second.id; - } - if (it->second.done) { done = true; } - } - len += 1; - } - return match_len; + if (it->second.done) { done = true; } + } + len += 1; } + return match_len; + } - size_t size() const { return dic_.size(); } + size_t size() const { return dic_.size(); } + size_t items_count() const { return items_count_; } + + friend struct ComputeFirstSet; private: - std::string to_lower(std::string s) const { - for (char &c : s) { - c = std::tolower(c); - } - return s; - } + struct Info { + bool done; + bool match; + size_t id; + }; - struct Info { - bool done; - bool match; - size_t id; - }; + // TODO: Use unordered_map when heterogeneous lookup is supported in C++20 + // std::unordered_map dic_; + std::map> dic_; - //! \todo Use unordered_map when heterogeneous lookup is supported in C++20 - //! \todo std::unordered_map dic_; - std::map> dic_; - - bool ignore_case_; + bool ignore_case_; + size_t items_count_; + size_t max_len_ = 0; }; /*----------------------------------------------------------------------------- @@ -456,21 +472,21 @@ private: * Line information utility function */ inline std::pair line_info(const char *start, const char *cur) { - auto p = start; - auto col_ptr = p; - auto no = 1; + auto p = start; + auto col_ptr = p; + auto no = 1; - while (p < cur) { - if (*p == '\n') { - no++; - col_ptr = p + 1; - } - p++; + while (p < cur) { + if (*p == '\n') { + no++; + col_ptr = p + 1; } + p++; + } - auto col = codepoint_count(col_ptr, p - col_ptr) + 1; + auto col = codepoint_count(col_ptr, p - col_ptr) + 1; - return std::pair(no, col); + return std::pair(no, col); } /* @@ -478,19 +494,19 @@ inline std::pair line_info(const char *start, const char *cur) { */ inline constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h) { - return (l == 0) ? h - : str2tag_core(s + 1, l - 1, - (h * 33) ^ static_cast(*s)); + return (l == 0) ? h + : str2tag_core(s + 1, l - 1, + (h * 33) ^ static_cast(*s)); } inline constexpr unsigned int str2tag(std::string_view sv) { - return str2tag_core(sv.data(), sv.size(), 0); + return str2tag_core(sv.data(), sv.size(), 0); } namespace udl { -inline constexpr unsigned int operator"" _(const char *s, size_t l) { - return str2tag_core(s, l, 0); +inline constexpr unsigned int operator""_(const char *s, size_t l) { + return str2tag_core(s, l, 0); } } // namespace udl @@ -501,126 +517,113 @@ inline constexpr unsigned int operator"" _(const char *s, size_t l) { class Context; struct SemanticValues : protected std::vector { - SemanticValues() = default; - SemanticValues(Context *c) : c_(c) {} + SemanticValues() = default; + SemanticValues(Context *c) : c_(c) {} - // Input text - const char *path = nullptr; - const char *ss = nullptr; + // Input text + const char *path = nullptr; + const char *ss = nullptr; - // Matched string - std::string_view sv() const { return sv_; } + // Matched string + std::string_view sv() const { return sv_; } - // Definition name - const std::string &name() const { return name_; } + // Definition name + const std::string &name() const { return name_; } - std::vector tags; + std::vector tags; - // Line number and column at which the matched string is - std::pair line_info() const; + // Line number and column at which the matched string is + std::pair line_info() const; - // Choice count - size_t choice_count() const { return choice_count_; } + // Choice count + size_t choice_count() const { return choice_count_; } - // Choice number (0 based index) - size_t choice() const { return choice_; } + // Choice number (0 based index) + size_t choice() const { return choice_; } - // Tokens - std::vector tokens; + // Tokens + std::vector tokens; - std::string_view token(size_t id = 0) const { - if (tokens.empty()) { return sv_; } - assert(id < tokens.size()); - return tokens[id]; + std::string_view token(size_t id = 0) const { + if (tokens.empty()) { return sv_; } + assert(id < tokens.size()); + return tokens[id]; + } + + // Token conversion + std::string token_to_string(size_t id = 0) const { + return std::string(token(id)); + } + + template T token_to_number() const { + return token_to_number_(token()); + } + + // Transform the semantic value vector to another vector + template + std::vector transform(size_t beg = 0, + size_t end = static_cast(-1)) const { + std::vector r; + end = (std::min)(end, size()); + for (size_t i = beg; i < end; i++) { + r.emplace_back(std::any_cast((*this)[i])); } + return r; + } - // Token conversion - std::string token_to_string(size_t id = 0) const { - return std::string(token(id)); - } - - template T token_to_number() const { - return token_to_number_(token()); - } - - // Transform the semantic value vector to another vector - template - std::vector transform(size_t beg = 0, - size_t end = static_cast(-1)) const { - std::vector r; - end = (std::min)(end, size()); - for (size_t i = beg; i < end; i++) { - r.emplace_back(std::any_cast((*this)[i])); - } - return r; - } - - void append(SemanticValues &chvs) { - sv_ = chvs.sv_; - for (auto &v : chvs) { - emplace_back(std::move(v)); - } - for (auto &tag : chvs.tags) { - tags.emplace_back(std::move(tag)); - } - for (auto &tok : chvs.tokens) { - tokens.emplace_back(std::move(tok)); - } - } - - using std::vector::iterator; - using std::vector::const_iterator; - using std::vector::size; - using std::vector::empty; - using std::vector::assign; - using std::vector::begin; - using std::vector::end; - using std::vector::rbegin; - using std::vector::rend; - using std::vector::operator[]; - using std::vector::at; - using std::vector::resize; - using std::vector::front; - using std::vector::back; - using std::vector::push_back; - using std::vector::pop_back; - using std::vector::insert; - using std::vector::erase; - using std::vector::clear; - using std::vector::swap; - using std::vector::emplace; - using std::vector::emplace_back; + using std::vector::iterator; + using std::vector::const_iterator; + using std::vector::size; + using std::vector::empty; + using std::vector::assign; + using std::vector::begin; + using std::vector::end; + using std::vector::rbegin; + using std::vector::rend; + using std::vector::operator[]; + using std::vector::at; + using std::vector::resize; + using std::vector::front; + using std::vector::back; + using std::vector::push_back; + using std::vector::pop_back; + using std::vector::insert; + using std::vector::erase; + using std::vector::clear; + using std::vector::swap; + using std::vector::emplace; + using std::vector::emplace_back; private: - friend class Context; - friend class Dictionary; - friend class Sequence; - friend class PrioritizedChoice; - friend class Repetition; - friend class Holder; - friend class PrecedenceClimbing; + friend class Context; + friend class Dictionary; + friend class Sequence; + friend class PrioritizedChoice; + friend class Repetition; + friend class Holder; + friend class PrecedenceClimbing; - Context *c_ = nullptr; - std::string_view sv_; - size_t choice_count_ = 0; - size_t choice_ = 0; - std::string name_; + Context *c_ = nullptr; + std::string_view sv_; + size_t choice_count_ = 0; + size_t choice_ = 0; + std::string name_; }; /* * Semantic action */ template std::any call(F fn, Args &&...args) { - using R = decltype(fn(std::forward(args)...)); - if constexpr (std::is_void::value) { - fn(std::forward(args)...); - return std::any(); - } else if constexpr (std::is_same::type, - std::any>::value) { - return fn(std::forward(args)...); - } else { - return std::any(fn(std::forward(args)...)); - } + using R = decltype(fn(std::forward(args)...)); + if constexpr (std::is_void::value) { + fn(std::forward(args)...); + return std::any(); + } else if constexpr (std::is_same::type, + std::any>::value) { + return fn(std::forward(args)...); + } else { + return std::any(fn(std::forward(args)...)); + } } template @@ -637,30 +640,74 @@ struct argument_count class Action { public: - Action() = default; - Action(Action &&rhs) = default; - template Action(F fn) : fn_(make_adaptor(fn)) {} - template void operator=(F fn) { fn_ = make_adaptor(fn); } - Action &operator=(const Action &rhs) = default; + Action() = default; + Action(Action &&rhs) = default; + template Action(F fn) : fn_(make_adaptor(fn)) {} + template void operator=(F fn) { fn_ = make_adaptor(fn); } + Action &operator=(const Action &rhs) = default; - operator bool() const { return bool(fn_); } + operator bool() const { return bool(fn_); } - std::any operator()(SemanticValues &vs, std::any &dt) const { - return fn_(vs, dt); - } + std::any operator()(SemanticValues &vs, std::any &dt, + const std::any &predicate_data) const { + return fn_(vs, dt, predicate_data); + } private: - using Fty = std::function; + using Fty = std::function; - template Fty make_adaptor(F fn) { - if constexpr (argument_count::value == 1) { - return [fn](auto &vs, auto & /*dt*/) { return call(fn, vs); }; - } else { - return [fn](auto &vs, auto &dt) { return call(fn, vs, dt); }; - } + template Fty make_adaptor(F fn) { + if constexpr (argument_count::value == 1) { + return [fn](auto &vs, auto & /*dt*/, const auto & /*predicate_data*/) { + return call(fn, vs); + }; + } else if constexpr (argument_count::value == 2) { + return [fn](auto &vs, auto &dt, const auto & /*predicate_data*/) { + return call(fn, vs, dt); + }; + } else { + return [fn](auto &vs, auto &dt, const auto &predicate_data) { + return call(fn, vs, dt, predicate_data); + }; } + } - Fty fn_; + Fty fn_; +}; + +class Predicate { +public: + Predicate() = default; + Predicate(Predicate &&rhs) = default; + template Predicate(F fn) : fn_(make_adaptor(fn)) {} + template void operator=(F fn) { fn_ = make_adaptor(fn); } + Predicate &operator=(const Predicate &rhs) = default; + + operator bool() const { return bool(fn_); } + + bool operator()(const SemanticValues &vs, const std::any &dt, + std::string &msg, std::any &predicate_data) const { + return fn_(vs, dt, msg, predicate_data); + } + +private: + using Fty = std::function; + + template Fty make_adaptor(F fn) { + if constexpr (argument_count::value == 3) { + return [fn](const auto &vs, const auto &dt, auto &msg, + auto & /*predicate_data*/) { return fn(vs, dt, msg); }; + } else { + return [fn](const auto &vs, const auto &dt, auto &msg, + auto &predicate_data) { + return fn(vs, dt, msg, predicate_data); + }; + } + } + + Fty fn_; }; /* @@ -682,67 +729,67 @@ using Log = std::function> expected_tokens; - const char *message_pos = nullptr; - std::string message; - std::string label; - const char *last_output_pos = nullptr; - bool keep_previous_token = false; + const char *error_pos = nullptr; + std::vector> expected_tokens; + const char *message_pos = nullptr; + std::string message; + std::string label; + const char *last_output_pos = nullptr; + bool keep_previous_token = false; - void clear() { - error_pos = nullptr; - expected_tokens.clear(); - message_pos = nullptr; - message.clear(); + void clear() { + error_pos = nullptr; + expected_tokens.clear(); + message_pos = nullptr; + message.clear(); + } + + void add(const char *error_literal, const Definition *error_rule) { + for (const auto &[t, r] : expected_tokens) { + if (t == error_literal && r == error_rule) { return; } } + expected_tokens.emplace_back(error_literal, error_rule); + } - void add(const char *error_literal, const Definition *error_rule) { - for (const auto &[t, r] : expected_tokens) { - if (t == error_literal && r == error_rule) { return; } - } - expected_tokens.emplace_back(error_literal, error_rule); - } - - void output_log(const Log &log, const char *s, size_t n); + void output_log(const Log &log, const char *s, size_t n); private: - int cast_char(char c) const { return static_cast(c); } + int cast_char(char c) const { return static_cast(c); } - std::string heuristic_error_token(const char *s, size_t n, - const char *pos) const { - auto len = n - std::distance(s, pos); - if (len) { - size_t i = 0; - auto c = cast_char(pos[i++]); - if (!std::ispunct(c) && !std::isspace(c)) { - while (i < len && !std::ispunct(cast_char(pos[i])) && - !std::isspace(cast_char(pos[i]))) { - i++; - } - } - - size_t count = CPPPEGLIB_HEURISTIC_ERROR_TOKEN_MAX_CHAR_COUNT; - size_t j = 0; - while (count > 0 && j < i) { - j += codepoint_length(&pos[j], i - j); - count--; - } - - return escape_characters(pos, j); + std::string heuristic_error_token(const char *s, size_t n, + const char *pos) const { + auto len = n - std::distance(s, pos); + if (len) { + size_t i = 0; + auto c = cast_char(pos[i++]); + if (!std::ispunct(c) && !std::isspace(c)) { + while (i < len && !std::ispunct(cast_char(pos[i])) && + !std::isspace(cast_char(pos[i]))) { + i++; } - return std::string(); - } + } - std::string replace_all(std::string str, const std::string &from, - const std::string &to) const { - size_t pos = 0; - while ((pos = str.find(from, pos)) != std::string::npos) { - str.replace(pos, from.length(), to); - pos += to.length(); - } - return str; + size_t count = CPPPEGLIB_HEURISTIC_ERROR_TOKEN_MAX_CHAR_COUNT; + size_t j = 0; + while (count > 0 && j < i) { + j += codepoint_length(&pos[j], i - j); + count--; + } + + return escape_characters(pos, j); } + return std::string(); + } + + std::string replace_all(std::string str, const std::string &from, + const std::string &to) const { + size_t pos = 0; + while ((pos = str.find(from, pos)) != std::string::npos) { + str.replace(pos, from.length(), to); + pos += to.length(); + } + return str; + } }; /* @@ -762,211 +809,269 @@ using TracerStartOrEnd = std::function; class Context { public: - const char *path; - const char *s; - const size_t l; + const char *path; + const char *s; + const size_t l; - ErrorInfo error_info; - bool recovered = false; + ErrorInfo error_info; + bool recovered = false; - std::vector> value_stack; - size_t value_stack_size = 0; + std::vector> value_stack; + size_t value_stack_size = 0; - std::vector rule_stack; - std::vector>> args_stack; + std::vector rule_stack; + std::vector>> args_stack; - size_t in_token_boundary_count = 0; + size_t in_token_boundary_count = 0; - std::shared_ptr whitespaceOpe; - bool in_whitespace = false; + std::shared_ptr whitespaceOpe; + bool in_whitespace = false; - std::shared_ptr wordOpe; + std::shared_ptr wordOpe; - std::vector> capture_scope_stack; - size_t capture_scope_stack_size = 0; + std::vector> capture_entries; - std::vector cut_stack; + std::vector cut_stack; - const size_t def_count; - const bool enablePackratParsing; - std::vector cache_registered; - std::vector cache_success; + const size_t def_count; + const bool enablePackratParsing; + std::vector cache_registered; + std::vector cache_success; - std::map, std::tuple> - cache_values; + std::map, std::tuple> + cache_values; - TracerEnter tracer_enter; - TracerLeave tracer_leave; - std::any trace_data; - const bool verbose_trace; + // Left recursion support + struct LRMemo { + size_t len = static_cast(-1); + std::any val; + }; + std::map, LRMemo> lr_memo; - Log log; + // Rules whose lr_memo was hit during the current parse scope. + // Used to track LR cycle membership. + std::set lr_refs_hit; - Context(const char *path, const char *s, size_t l, size_t def_count, - std::shared_ptr whitespaceOpe, std::shared_ptr wordOpe, - bool enablePackratParsing, TracerEnter tracer_enter, - TracerLeave tracer_leave, std::any trace_data, bool verbose_trace, - Log log) - : path(path), s(s), l(l), whitespaceOpe(whitespaceOpe), wordOpe(wordOpe), - def_count(def_count), enablePackratParsing(enablePackratParsing), - cache_registered(enablePackratParsing ? def_count * (l + 1) : 0), - cache_success(enablePackratParsing ? def_count * (l + 1) : 0), - tracer_enter(tracer_enter), tracer_leave(tracer_leave), - trace_data(trace_data), verbose_trace(verbose_trace), log(log) { + // Rules currently in their seeding/growing phase at a given position. + // Protected from having their lr_memo erased by inner growers. + std::set> lr_active_seeds; - push_args({}); - push_capture_scope(); + void clear_packrat_cache(const char *pos, size_t def_id) { + if (!enablePackratParsing) { return; } + auto col = static_cast(pos - s); + auto idx = def_count * col + def_id; + if (idx < cache_registered.size()) { + cache_registered[idx] = false; + cache_success[idx] = false; + } + cache_values.erase(std::make_pair(col, def_id)); + } + + void write_packrat_cache(const char *pos, size_t def_id, size_t len, + const std::any &val) { + if (!enablePackratParsing) { return; } + auto col = pos - s; + auto idx = def_count * static_cast(col) + def_id; + if (idx >= cache_registered.size()) { return; } + cache_registered[idx] = true; + cache_success[idx] = true; + auto key = std::pair(col, def_id); + cache_values[key] = std::pair(len, val); + } + + TracerEnter tracer_enter; + TracerLeave tracer_leave; + std::any trace_data; + const bool verbose_trace; + + Log log; + + Context(const char *path, const char *s, size_t l, size_t def_count, + std::shared_ptr whitespaceOpe, std::shared_ptr wordOpe, + bool enablePackratParsing, TracerEnter tracer_enter, + TracerLeave tracer_leave, std::any trace_data, bool verbose_trace, + Log log) + : path(path), s(s), l(l), whitespaceOpe(whitespaceOpe), wordOpe(wordOpe), + def_count(def_count), enablePackratParsing(enablePackratParsing), + cache_registered(enablePackratParsing ? def_count * (l + 1) : 0), + cache_success(enablePackratParsing ? def_count * (l + 1) : 0), + tracer_enter(tracer_enter), tracer_leave(tracer_leave), + trace_data(trace_data), verbose_trace(verbose_trace), log(log) { + + push_args({}); + } + + ~Context() { + assert(!value_stack_size); + assert(cut_stack.empty()); + } + + Context(const Context &) = delete; + Context(Context &&) = delete; + Context operator=(const Context &) = delete; + + // Per-rule packrat stats (populated when packrat_stats is non-null) + struct PackratStats { + size_t hits = 0; + size_t misses = 0; + }; + std::vector *packrat_stats = nullptr; + + // Per-rule packrat filter: if set, only rules with filter[def_id]=true + // use full memoization (cache_values map). Others use bitvector-only + // re-entry guard. + const std::vector *packrat_rule_filter = nullptr; + + template + void packrat(const char *a_s, size_t def_id, size_t &len, std::any &val, + T fn) { + if (!enablePackratParsing) { + fn(val); + return; } - ~Context() { - pop_capture_scope(); + auto col = a_s - s; + auto idx = def_count * static_cast(col) + def_id; - assert(!value_stack_size); - assert(!capture_scope_stack_size); - assert(cut_stack.empty()); + if (cache_registered[idx]) { + if (packrat_stats && def_id < packrat_stats->size()) { + (*packrat_stats)[def_id].hits++; + } + if (cache_success[idx]) { + auto key = std::pair(col, def_id); + std::tie(len, val) = cache_values[key]; + return; + } else { + len = static_cast(-1); + return; + } + } else { + // Pre-register as failure (re-entry guard for all rules) + cache_registered[idx] = true; + cache_success[idx] = false; + + if (packrat_stats && def_id < packrat_stats->size()) { + (*packrat_stats)[def_id].misses++; + } + + fn(val); + + bool full_memo = + !packrat_rule_filter || (def_id < packrat_rule_filter->size() && + (*packrat_rule_filter)[def_id]); + if (full_memo) { + if (success(len)) { write_packrat_cache(a_s, def_id, len, val); } + } else { + // Guard-only: undo registration so future calls re-parse + cache_registered[idx] = false; + } + return; + } + } + + // Semantic values + SemanticValues &push_semantic_values_scope() { + assert(value_stack_size <= value_stack.size()); + if (value_stack_size == value_stack.size()) { + value_stack.emplace_back(std::make_shared(this)); + } else { + auto &vs = *value_stack[value_stack_size]; + if (!vs.empty()) { + vs.clear(); + if (!vs.tags.empty()) { vs.tags.clear(); } + } + vs.sv_ = std::string_view(); + vs.choice_count_ = 0; + vs.choice_ = 0; + if (!vs.tokens.empty()) { vs.tokens.clear(); } } - Context(const Context &) = delete; - Context(Context &&) = delete; - Context operator=(const Context &) = delete; + auto &vs = *value_stack[value_stack_size++]; + vs.path = path; + vs.ss = s; + return vs; + } - template - void packrat(const char *a_s, size_t def_id, size_t &len, std::any &val, - T fn) { - if (!enablePackratParsing) { - fn(val); - return; - } + void pop_semantic_values_scope() { value_stack_size--; } - auto col = a_s - s; - auto idx = def_count * static_cast(col) + def_id; + // Arguments + void push_args(std::vector> &&args) { + args_stack.emplace_back(std::move(args)); + } - if (cache_registered[idx]) { - if (cache_success[idx]) { - auto key = std::pair(col, def_id); - std::tie(len, val) = cache_values[key]; - return; - } else { - len = static_cast(-1); - return; - } - } else { - fn(val); - cache_registered[idx] = true; - cache_success[idx] = success(len); - if (success(len)) { - auto key = std::pair(col, def_id); - cache_values[key] = std::pair(len, val); - } - return; - } - } + void pop_args() { args_stack.pop_back(); } - SemanticValues &push() { - push_capture_scope(); - return push_semantic_values_scope(); - } + const std::vector> &top_args() const { + return args_stack[args_stack.size() - 1]; + } - void pop() { - pop_capture_scope(); - pop_semantic_values_scope(); - } + // Snapshot/Rollback + struct Snapshot { + size_t sv_size; + size_t sv_tags_size; + size_t sv_tokens_size; + std::string_view sv_sv; + size_t choice_count; + size_t choice; + size_t capture_size; + }; - // Semantic values - SemanticValues &push_semantic_values_scope() { - assert(value_stack_size <= value_stack.size()); - if (value_stack_size == value_stack.size()) { - value_stack.emplace_back(std::make_shared(this)); - } else { - auto &vs = *value_stack[value_stack_size]; - if (!vs.empty()) { - vs.clear(); - if (!vs.tags.empty()) { vs.tags.clear(); } - } - vs.sv_ = std::string_view(); - vs.choice_count_ = 0; - vs.choice_ = 0; - if (!vs.tokens.empty()) { vs.tokens.clear(); } - } + Snapshot snapshot(const SemanticValues &vs) const { + return {vs.size(), vs.tags.size(), vs.tokens.size(), vs.sv_, + vs.choice_count_, vs.choice_, capture_entries.size()}; + } - auto &vs = *value_stack[value_stack_size++]; - vs.path = path; - vs.ss = s; - return vs; - } + void rollback(SemanticValues &vs, const Snapshot &snap) { + vs.resize(snap.sv_size); + vs.tags.resize(snap.sv_tags_size); + vs.tokens.resize(snap.sv_tokens_size); + vs.sv_ = snap.sv_sv; + vs.choice_count_ = snap.choice_count; + vs.choice_ = snap.choice; + capture_entries.resize(snap.capture_size); + } - void pop_semantic_values_scope() { value_stack_size--; } + // Skip trailing whitespace with trace suppression. + // Returns whitespace length, or -1 on failure. + // No-op (returns 0) if inside a token boundary or no whitespaceOpe. + size_t skip_whitespace(const char *a_s, size_t n, SemanticValues &vs, + std::any &dt); - // Arguments - void push_args(std::vector> &&args) { - args_stack.emplace_back(args); - } + // Error + void set_error_pos(const char *a_s, const char *literal = nullptr); - void pop_args() { args_stack.pop_back(); } + // Trace + void trace_enter(const Ope &ope, const char *a_s, size_t n, + const SemanticValues &vs, std::any &dt); + void trace_leave(const Ope &ope, const char *a_s, size_t n, + const SemanticValues &vs, std::any &dt, size_t len); + bool is_traceable(const Ope &ope) const; - const std::vector> &top_args() const { - return args_stack[args_stack.size() - 1]; - } + // Line info + std::pair line_info(const char *cur) const { + std::call_once(source_line_index_init_, [this]() { + for (size_t pos = 0; pos < l; pos++) { + if (s[pos] == '\n') { source_line_index.push_back(pos); } + } + source_line_index.push_back(l); + }); - // Capture scope - void push_capture_scope() { - assert(capture_scope_stack_size <= capture_scope_stack.size()); - if (capture_scope_stack_size == capture_scope_stack.size()) { - capture_scope_stack.emplace_back( - std::map()); - } else { - auto &cs = capture_scope_stack[capture_scope_stack_size]; - if (!cs.empty()) { cs.clear(); } - } - capture_scope_stack_size++; - } + auto pos = static_cast(std::distance(s, cur)); - void pop_capture_scope() { capture_scope_stack_size--; } + auto it = std::lower_bound( + source_line_index.begin(), source_line_index.end(), pos, + [](size_t element, size_t value) { return element < value; }); - void shift_capture_values() { - assert(capture_scope_stack_size >= 2); - auto curr = &capture_scope_stack[capture_scope_stack_size - 1]; - auto prev = curr - 1; - for (const auto &[k, v] : *curr) { - (*prev)[k] = v; - } - } + auto id = static_cast(std::distance(source_line_index.begin(), it)); + auto off = pos - (id == 0 ? 0 : source_line_index[id - 1] + 1); + return std::pair(id + 1, off + 1); + } - // Error - void set_error_pos(const char *a_s, const char *literal = nullptr); - - // Trace - void trace_enter(const Ope &ope, const char *a_s, size_t n, - const SemanticValues &vs, std::any &dt); - void trace_leave(const Ope &ope, const char *a_s, size_t n, - const SemanticValues &vs, std::any &dt, size_t len); - bool is_traceable(const Ope &ope) const; - - // Line info - std::pair line_info(const char *cur) const { - std::call_once(source_line_index_init_, [this]() { - for (size_t pos = 0; pos < l; pos++) { - if (s[pos] == '\n') { source_line_index.push_back(pos); } - } - source_line_index.push_back(l); - }); - - auto pos = static_cast(std::distance(s, cur)); - - auto it = std::lower_bound( - source_line_index.begin(), source_line_index.end(), pos, - [](size_t element, size_t value) { return element < value; }); - - auto id = static_cast(std::distance(source_line_index.begin(), it)); - auto off = pos - (id == 0 ? 0 : source_line_index[id - 1] + 1); - return std::pair(id + 1, off + 1); - } - - size_t next_trace_id = 0; - std::vector trace_ids; - bool ignore_trace_state = false; - mutable std::once_flag source_line_index_init_; - mutable std::vector source_line_index; + size_t next_trace_id = 0; + std::vector trace_ids; + bool ignore_trace_state = false; + mutable std::once_flag source_line_index_init_; + mutable std::vector source_line_index; }; /* @@ -974,416 +1079,590 @@ public: */ class Ope { public: - struct Visitor; + struct Visitor; - virtual ~Ope() = default; - size_t parse(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const; - virtual size_t parse_core(const char *s, size_t n, SemanticValues &vs, - Context &c, std::any &dt) const = 0; - virtual void accept(Visitor &v) = 0; + virtual ~Ope() = default; + size_t parse(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const; + virtual size_t parse_core(const char *s, size_t n, SemanticValues &vs, + Context &c, std::any &dt) const = 0; + virtual void accept(Visitor &v) = 0; + + bool is_token_boundary = false; + bool is_choice_like = false; +}; + +// Keyword-guarded identifier data, heap-allocated only for matching Sequences. +// Avoids bloating all Sequence objects with bitsets and keyword sets. +struct KeywordGuardData { + std::bitset<256> identifier_first; // first char of identifier + std::bitset<256> identifier_rest; // subsequent chars of identifier + std::vector exact_keywords; // single-word keywords (lowercase) + std::vector prefix_keywords; // first word of compound keywords + size_t min_keyword_len = 0; + size_t max_keyword_len = 0; + + static bool matches_any(const std::vector &keywords, + std::string_view input) { + return std::any_of(keywords.begin(), keywords.end(), + [&](const auto &kw) { return kw == input; }); + } }; class Sequence : public Ope { public: - template - Sequence(const Args &...args) - : opes_{static_cast>(args)...} {} - Sequence(const std::vector> &opes) : opes_(opes) {} - Sequence(std::vector> &&opes) : opes_(opes) {} + template + Sequence(const Args &...args) + : opes_{static_cast>(args)...} {} + Sequence(const std::vector> &opes) : opes_(opes) {} + Sequence(std::vector> &&opes) : opes_(std::move(opes)) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - auto &chvs = c.push_semantic_values_scope(); - auto se = scope_exit([&]() { c.pop_semantic_values_scope(); }); - size_t i = 0; - for (const auto &ope : opes_) { - auto len = ope->parse(s + i, n - i, chvs, c, dt); - if (fail(len)) { return len; } - i += len; - } - vs.append(chvs); - return i; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + // Keyword-guarded identifier fast path: + // Fuses !ReservedKeyword into scan-then-lookup + if (kw_guard_) { + if (auto result = parse_keyword_guarded(s, n, vs, c, dt)) { + return *result; + } + // nullopt means prefix keyword match — fall through to normal path } + size_t i = 0; + for (const auto &ope : opes_) { + auto len = ope->parse(s + i, n - i, vs, c, dt); + if (fail(len)) { return len; } + i += len; + } + return i; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::vector> opes_; + std::vector> opes_; + +private: + friend struct SetupFirstSets; + std::unique_ptr kw_guard_; + + // Returns parse result, or nullopt to fall through to normal path + std::optional parse_keyword_guarded(const char *s, size_t n, + SemanticValues &vs, Context &c, + std::any &dt) const { + const auto &kw = *kw_guard_; + if (n < 1 || !kw.identifier_first.test(static_cast(*s))) { + c.set_error_pos(s); + return static_cast(-1); + } + // Scan identifier using bitset + size_t id_len = 1; + while (id_len < n && + kw.identifier_rest.test(static_cast(s[id_len]))) { + id_len++; + } + // Skip keyword matching if identifier length is out of range + if (id_len >= kw.min_keyword_len && id_len <= kw.max_keyword_len) { + char lower_buf[64]; + std::unique_ptr lower_heap; + char *lower = lower_buf; + if (id_len > sizeof(lower_buf)) { + lower_heap.reset(new char[id_len]); + lower = lower_heap.get(); + } + std::transform(s, s + id_len, lower, [](unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + std::string_view lower_sv(lower, id_len); + + if (KeywordGuardData::matches_any(kw.exact_keywords, lower_sv)) { + c.set_error_pos(s); + return static_cast(-1); + } + if (KeywordGuardData::matches_any(kw.prefix_keywords, lower_sv)) { + return std::nullopt; + } + } + // Success: emit token and consume trailing whitespace + vs.tokens.emplace_back(std::string_view(s, id_len)); + auto wl = c.skip_whitespace(s + id_len, n - id_len, vs, dt); + if (fail(wl)) { return wl; } + return id_len + wl; + } +}; + +struct FirstSet { + // First-Set: set of possible first bytes for an expression. + // Used by PrioritizedChoice to skip alternatives that cannot match. + std::bitset<256> chars; // byte values that can appear as the first byte + bool can_be_empty = false; // true if the expression can match empty string + bool any_char = false; // true if any character can appear (cannot filter) + const char *first_literal = nullptr; // first literal for error reporting + const Definition *first_rule = + nullptr; // first token rule for error reporting + + void merge(const FirstSet &other) { + chars |= other.chars; + if (other.can_be_empty) { can_be_empty = true; } + if (other.any_char) { any_char = true; } + // Note: first_literal/first_rule are NOT merged — per-alternative + } }; class PrioritizedChoice : public Ope { public: - template - PrioritizedChoice(bool for_label, const Args &...args) - : opes_{static_cast>(args)...}, - for_label_(for_label) {} - PrioritizedChoice(const std::vector> &opes) - : opes_(opes) {} - PrioritizedChoice(std::vector> &&opes) : opes_(opes) {} + template + PrioritizedChoice(bool for_label, const Args &...args) + : opes_{static_cast>(args)...}, + for_label_(for_label) { + is_choice_like = true; + } + PrioritizedChoice(const std::vector> &opes) + : opes_(opes) { + is_choice_like = true; + } + PrioritizedChoice(std::vector> &&opes) + : opes_(std::move(opes)) { + is_choice_like = true; + } - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - size_t len = static_cast(-1); + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + size_t len = static_cast(-1); - if (!for_label_) { c.cut_stack.push_back(false); } - auto se1 = scope_exit([&]() { - if (!for_label_) { c.cut_stack.pop_back(); } - }); + if (!for_label_) { c.cut_stack.push_back(false); } + auto se = scope_exit([&]() { + if (!for_label_) { c.cut_stack.pop_back(); } + }); - size_t id = 0; - for (const auto &ope : opes_) { - if (!c.cut_stack.empty()) { c.cut_stack.back() = false; } - - auto &chvs = c.push(); - c.error_info.keep_previous_token = id > 0; - auto se2 = scope_exit([&]() { - c.pop(); - c.error_info.keep_previous_token = false; - }); - - len = ope->parse(s, n, chvs, c, dt); - - if (success(len)) { - vs.append(chvs); - vs.choice_count_ = opes_.size(); - vs.choice_ = id; - c.shift_capture_values(); - break; - } else if (!c.cut_stack.empty() && c.cut_stack.back()) { - break; + size_t id = 0; + for (const auto &ope : opes_) { + // First-Set filtering: skip if next byte cannot start this alternative + if (n > 0 && id < first_sets_.size()) { + const auto &fs = first_sets_[id]; + if (!fs.any_char && !fs.can_be_empty && + !fs.chars.test(static_cast(*s))) { + if (c.log && (fs.first_literal || fs.first_rule)) { + if (c.error_info.error_pos <= s) { + if (c.error_info.error_pos < s || !(id > 0)) { + c.error_info.error_pos = s; + c.error_info.expected_tokens.clear(); + } + if (fs.first_literal) { + c.error_info.add(fs.first_literal, nullptr); + } else { + c.error_info.add(nullptr, fs.first_rule); + } } - - id++; + } + id++; + continue; } + } - return len; + if (!c.cut_stack.empty()) { c.cut_stack.back() = false; } + + auto snap = c.snapshot(vs); + c.error_info.keep_previous_token = id > 0; + + len = ope->parse(s, n, vs, c, dt); + + if (success(len)) { + vs.choice_count_ = opes_.size(); + vs.choice_ = id; + break; + } + + c.rollback(vs, snap); + + if (!c.cut_stack.empty() && c.cut_stack.back()) { break; } + + id++; } - void accept(Visitor &v) override; + c.error_info.keep_previous_token = false; + return len; + } - size_t size() const { return opes_.size(); } + void accept(Visitor &v) override; - std::vector> opes_; - bool for_label_ = false; + size_t size() const { return opes_.size(); } + + std::vector> opes_; + bool for_label_ = false; + std::vector first_sets_; }; class Repetition : public Ope { public: - Repetition(const std::shared_ptr &ope, size_t min, size_t max) - : ope_(ope), min_(min), max_(max) {} + Repetition(const std::shared_ptr &ope, size_t min, size_t max) + : ope_(ope), min_(min), max_(max) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - size_t count = 0; - size_t i = 0; - while (count < min_) { - auto &chvs = c.push(); - auto se = scope_exit([&]() { c.pop(); }); - - auto len = ope_->parse(s + i, n - i, chvs, c, dt); - - if (success(len)) { - vs.append(chvs); - c.shift_capture_values(); - } else { - return len; - } - i += len; - count++; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + // ISpan fast path: tight loop for ASCII CharacterClass repetition. + // Safe because each ASCII match is exactly 1 byte, so byte count == match + // count. + if (span_bitset_) { + const auto &bitset = *span_bitset_; + size_t i = 0; + if (max_ == std::numeric_limits::max()) { + // Unbounded repetition (*, +): no per-iteration max check + while (i < n && bitset.test(static_cast(s[i]))) { + i++; } - - while (count < max_) { - auto &chvs = c.push(); - auto se = scope_exit([&]() { c.pop(); }); - - auto len = ope_->parse(s + i, n - i, chvs, c, dt); - - if (success(len)) { - vs.append(chvs); - c.shift_capture_values(); - } else { - break; - } - i += len; - count++; + } else { + auto limit = std::min(n, max_); + while (i < limit && bitset.test(static_cast(s[i]))) { + i++; } - return i; + } + if (i < min_) { + c.set_error_pos(s + i); + return static_cast(-1); + } + return i; } - void accept(Visitor &v) override; - - bool is_zom() const { - return min_ == 0 && max_ == std::numeric_limits::max(); + size_t count = 0; + size_t i = 0; + while (count < min_) { + auto len = ope_->parse(s + i, n - i, vs, c, dt); + if (fail(len)) { return len; } + i += len; + count++; } - static std::shared_ptr zom(const std::shared_ptr &ope) { - return std::make_shared(ope, 0, - std::numeric_limits::max()); + while (count < max_) { + auto snap = c.snapshot(vs); + auto len = ope_->parse(s + i, n - i, vs, c, dt); + if (fail(len)) { + c.rollback(vs, snap); + break; + } + i += len; + count++; } + return i; + } - static std::shared_ptr oom(const std::shared_ptr &ope) { - return std::make_shared(ope, 1, - std::numeric_limits::max()); - } + void accept(Visitor &v) override; - static std::shared_ptr opt(const std::shared_ptr &ope) { - return std::make_shared(ope, 0, 1); - } + bool is_zom() const { + return min_ == 0 && max_ == std::numeric_limits::max(); + } - std::shared_ptr ope_; - size_t min_; - size_t max_; + static std::shared_ptr zom(const std::shared_ptr &ope) { + return std::make_shared(ope, 0, + std::numeric_limits::max()); + } + + static std::shared_ptr oom(const std::shared_ptr &ope) { + return std::make_shared(ope, 1, + std::numeric_limits::max()); + } + + static std::shared_ptr opt(const std::shared_ptr &ope) { + return std::make_shared(ope, 0, 1); + } + + std::shared_ptr ope_; + size_t min_; + size_t max_; + const std::bitset<256> *span_bitset_ = + nullptr; // non-owning, set by SetupFirstSets }; class AndPredicate : public Ope { public: - AndPredicate(const std::shared_ptr &ope) : ope_(ope) {} + AndPredicate(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any &dt) const override { - auto &chvs = c.push(); - auto se = scope_exit([&]() { c.pop(); }); - - auto len = ope_->parse(s, n, chvs, c, dt); - - if (success(len)) { - return 0; - } else { - return len; - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto snap = c.snapshot(vs); + auto len = ope_->parse(s, n, vs, c, dt); + c.rollback(vs, snap); // Always rollback — predicates consume nothing + if (success(len)) { + return 0; + } else { + return len; } + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class NotPredicate : public Ope { public: - NotPredicate(const std::shared_ptr &ope) : ope_(ope) {} + NotPredicate(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any &dt) const override { - auto &chvs = c.push(); - auto se = scope_exit([&]() { c.pop(); }); - auto len = ope_->parse(s, n, chvs, c, dt); - if (success(len)) { - c.set_error_pos(s); - return static_cast(-1); - } else { - return 0; - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto snap = c.snapshot(vs); + auto len = ope_->parse(s, n, vs, c, dt); + c.rollback(vs, snap); // Always rollback — predicates consume nothing + if (success(len)) { + c.set_error_pos(s); + return static_cast(-1); + } else { + return 0; } + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class Dictionary : public Ope, public std::enable_shared_from_this { public: - Dictionary(const std::vector &v, bool ignore_case) - : trie_(v, ignore_case) {} + Dictionary(const std::vector &v, bool ignore_case) + : trie_(v, ignore_case) { + is_choice_like = true; + } - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - Trie trie_; + Trie trie_; }; class LiteralString : public Ope, public std::enable_shared_from_this { public: - LiteralString(std::string &&s, bool ignore_case) - : lit_(s), ignore_case_(ignore_case), is_word_(false) {} + LiteralString(std::string &&s, bool ignore_case) + : lit_(std::move(s)), ignore_case_(ignore_case), + lower_lit_(ignore_case ? to_lower(lit_) : std::string()), + is_word_(false) {} - LiteralString(const std::string &s, bool ignore_case) - : lit_(s), ignore_case_(ignore_case), is_word_(false) {} + LiteralString(const std::string &s, bool ignore_case) + : lit_(s), ignore_case_(ignore_case), + lower_lit_(ignore_case ? to_lower(lit_) : std::string()), + is_word_(false) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::string lit_; - bool ignore_case_; - mutable std::once_flag init_is_word_; - mutable bool is_word_; + std::string lit_; + bool ignore_case_; + std::string lower_lit_; // pre-computed for ignore_case + mutable std::once_flag init_is_word_; + mutable bool is_word_; }; class CharacterClass : public Ope, public std::enable_shared_from_this { public: - CharacterClass(const std::string &s, bool negated, bool ignore_case) - : negated_(negated), ignore_case_(ignore_case) { - auto chars = decode(s.data(), s.length()); - auto i = 0u; - while (i < chars.size()) { - if (i + 2 < chars.size() && chars[i + 1] == '-') { - auto cp1 = chars[i]; - auto cp2 = chars[i + 2]; - ranges_.emplace_back(std::pair(cp1, cp2)); - i += 3; - } else { - auto cp = chars[i]; - ranges_.emplace_back(std::pair(cp, cp)); - i += 1; - } - } - assert(!ranges_.empty()); + CharacterClass(const std::string &s, bool negated, bool ignore_case) + : negated_(negated), ignore_case_(ignore_case) { + auto chars = decode(s.data(), s.length()); + auto i = 0u; + while (i < chars.size()) { + if (i + 2 < chars.size() && chars[i + 1] == '-') { + auto cp1 = chars[i]; + auto cp2 = chars[i + 2]; + ranges_.emplace_back(std::pair(cp1, cp2)); + i += 3; + } else { + auto cp = chars[i]; + ranges_.emplace_back(std::pair(cp, cp)); + i += 1; + } + } + assert(!ranges_.empty()); + setup_ascii_bitset(); + } + + CharacterClass(const std::vector> &ranges, + bool negated, bool ignore_case) + : ranges_(ranges), negated_(negated), ignore_case_(ignore_case) { + assert(!ranges_.empty()); + setup_ascii_bitset(); + } + + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + if (n < 1) { + c.set_error_pos(s); + return static_cast(-1); } - CharacterClass(const std::vector> &ranges, - bool negated, bool ignore_case) - : ranges_(ranges), negated_(negated), ignore_case_(ignore_case) { - assert(!ranges_.empty()); - } - - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any & /*dt*/) const override { - if (n < 1) { - c.set_error_pos(s); - return static_cast(-1); - } - - char32_t cp = 0; - auto len = decode_codepoint(s, n, cp); - - for (const auto &range : ranges_) { - if (in_range(range, cp)) { - if (negated_) { - c.set_error_pos(s); - return static_cast(-1); - } else { - return len; - } - } - } + char32_t cp = 0; + auto len = decode_codepoint(s, n, cp); + for (const auto &range : ranges_) { + if (in_range(range, cp)) { if (negated_) { - return len; + c.set_error_pos(s); + return static_cast(-1); } else { - c.set_error_pos(s); - return static_cast(-1); + return len; } + } } - void accept(Visitor &v) override; + if (negated_) { + return len; + } else { + c.set_error_pos(s); + return static_cast(-1); + } + } + + void accept(Visitor &v) override; + + friend struct ComputeFirstSet; + + bool is_ascii_only() const { return is_ascii_only_; } + const std::bitset<256> &ascii_bitset() const { return ascii_bitset_; } private: - bool in_range(const std::pair &range, char32_t cp) const { - if (ignore_case_) { - auto cpl = std::tolower(cp); - return std::tolower(range.first) <= cpl && - cpl <= std::tolower(range.second); - } else { - return range.first <= cp && cp <= range.second; - } + bool in_range(const std::pair &range, char32_t cp) const { + if (ignore_case_) { + auto cpl = std::tolower(cp); + return std::tolower(range.first) <= cpl && + cpl <= std::tolower(range.second); + } else { + return range.first <= cp && cp <= range.second; } + } - std::vector> ranges_; - bool negated_; - bool ignore_case_; + void setup_ascii_bitset() { + if (negated_) { return; } // negated classes can match non-ASCII + for (const auto &[lo, hi] : ranges_) { + if (lo > 0x7F || hi > 0x7F) { return; } + } + is_ascii_only_ = true; + for (const auto &[lo, hi] : ranges_) { + for (auto cp = lo; cp <= hi; cp++) { + auto ch = static_cast(cp); + ascii_bitset_.set(ch); + if (ignore_case_) { + ascii_bitset_.set(static_cast(std::toupper(ch))); + ascii_bitset_.set(static_cast(std::tolower(ch))); + } + } + } + } + + std::vector> ranges_; + bool negated_; + bool ignore_case_; + std::bitset<256> ascii_bitset_; + bool is_ascii_only_ = false; }; class Character : public Ope, public std::enable_shared_from_this { public: - Character(char ch) : ch_(ch) {} + Character(char32_t ch) : ch_(ch) {} - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any & /*dt*/) const override { - if (n < 1 || s[0] != ch_) { - c.set_error_pos(s); - return static_cast(-1); - } - return 1; + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + if (n < 1) { + c.set_error_pos(s); + return static_cast(-1); } - void accept(Visitor &v) override; + char32_t cp = 0; + auto len = decode_codepoint(s, n, cp); - char ch_; + if (cp != ch_) { + c.set_error_pos(s); + return static_cast(-1); + } + return len; + } + + void accept(Visitor &v) override; + + char32_t ch_; }; class AnyCharacter : public Ope, public std::enable_shared_from_this { public: - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any & /*dt*/) const override { - auto len = codepoint_length(s, n); - if (len < 1) { - c.set_error_pos(s); - return static_cast(-1); - } - return len; + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + auto len = codepoint_length(s, n); + if (len < 1) { + c.set_error_pos(s); + return static_cast(-1); } + return len; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; }; class CaptureScope : public Ope { public: - CaptureScope(const std::shared_ptr &ope) : ope_(ope) {} + CaptureScope(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - c.push_capture_scope(); - auto se = scope_exit([&]() { c.pop_capture_scope(); }); - return ope_->parse(s, n, vs, c, dt); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto cap_snap = c.capture_entries.size(); + auto len = ope_->parse(s, n, vs, c, dt); + c.capture_entries.resize(cap_snap); // Always rollback (isolation) + return len; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class Capture : public Ope { public: - using MatchAction = std::function; + using MatchAction = std::function; - Capture(const std::shared_ptr &ope, MatchAction ma) - : ope_(ope), match_action_(ma) {} + Capture(const std::shared_ptr &ope, MatchAction ma) + : ope_(ope), match_action_(ma) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - auto len = ope_->parse(s, n, vs, c, dt); - if (success(len) && match_action_) { match_action_(s, len, c); } - return len; - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto len = ope_->parse(s, n, vs, c, dt); + if (success(len) && match_action_) { match_action_(s, len, c); } + return len; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; - MatchAction match_action_; + std::shared_ptr ope_; + MatchAction match_action_; }; class TokenBoundary : public Ope { public: - TokenBoundary(const std::shared_ptr &ope) : ope_(ope) {} + TokenBoundary(const std::shared_ptr &ope) : ope_(ope) { + is_token_boundary = true; + } - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class Ignore : public Ope { public: - Ignore(const std::shared_ptr &ope) : ope_(ope) {} + Ignore(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any &dt) const override { - auto &chvs = c.push_semantic_values_scope(); - auto se = scope_exit([&]() { c.pop_semantic_values_scope(); }); - return ope_->parse(s, n, chvs, c, dt); - } + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any &dt) const override { + auto &chvs = c.push_semantic_values_scope(); + auto se = scope_exit([&]() { c.pop_semantic_values_scope(); }); + return ope_->parse(s, n, chvs, c, dt); + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; using Parser = std::function - fn_; + User(Parser fn) : fn_(fn) {} + size_t parse_core(const char *s, size_t n, SemanticValues &vs, + Context & /*c*/, std::any &dt) const override { + assert(fn_); + return fn_(s, n, vs, dt); + } + void accept(Visitor &v) override; + std::function + fn_; }; class WeakHolder : public Ope { public: - WeakHolder(const std::shared_ptr &ope) : weak_(ope) {} + WeakHolder(const std::shared_ptr &ope) : weak_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - auto ope = weak_.lock(); - assert(ope); - return ope->parse(s, n, vs, c, dt); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto ope = weak_.lock(); + assert(ope); + return ope->parse(s, n, vs, c, dt); + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::weak_ptr weak_; + std::weak_ptr weak_; }; class Holder : public Ope { public: - Holder(Definition *outer) : outer_(outer) {} + Holder(Definition *outer) : outer_(outer) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::any reduce(SemanticValues &vs, std::any &dt) const; + std::any reduce(SemanticValues &vs, std::any &dt, + const std::any &predicate_data) const; - const std::string &name() const; - const std::string &trace_name() const; + const std::string &name() const; + const std::string &trace_name() const; - std::shared_ptr ope_; - Definition *outer_; - mutable std::once_flag trace_name_init_; - mutable std::string trace_name_; + std::shared_ptr ope_; + Definition *outer_; + mutable std::once_flag trace_name_init_; + mutable std::string trace_name_; - friend class Definition; + friend class Definition; }; using Grammar = std::unordered_map; class Reference : public Ope, public std::enable_shared_from_this { public: - Reference(const Grammar &grammar, const std::string &name, const char *s, - bool is_macro, const std::vector> &args) - : grammar_(grammar), name_(name), s_(s), is_macro_(is_macro), args_(args), - rule_(nullptr), iarg_(0) {} + Reference(const Grammar &grammar, const std::string &name, const char *s, + bool is_macro, const std::vector> &args) + : grammar_(grammar), name_(name), s_(s), is_macro_(is_macro), args_(args), + rule_(nullptr), iarg_(0) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr get_core_operator() const; + std::shared_ptr get_core_operator() const; - const Grammar &grammar_; - const std::string name_; - const char *s_; + const Grammar &grammar_; + const std::string name_; + const char *s_; - const bool is_macro_; - const std::vector> args_; + const bool is_macro_; + const std::vector> args_; - Definition *rule_; - size_t iarg_; + Definition *rule_; + size_t iarg_; }; class Whitespace : public Ope { public: - Whitespace(const std::shared_ptr &ope) : ope_(ope) {} + Whitespace(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - if (c.in_whitespace) { return 0; } - c.in_whitespace = true; - auto se = scope_exit([&]() { c.in_whitespace = false; }); - return ope_->parse(s, n, vs, c, dt); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + if (c.in_whitespace) { return 0; } + c.in_whitespace = true; + auto se = scope_exit([&]() { c.in_whitespace = false; }); + return ope_->parse(s, n, vs, c, dt); + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class BackReference : public Ope { public: - BackReference(std::string &&name) : name_(name) {} + BackReference(std::string &&name) : name_(std::move(name)) {} - BackReference(const std::string &name) : name_(name) {} + BackReference(const std::string &name) : name_(name) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::string name_; + std::string name_; }; class PrecedenceClimbing : public Ope { public: - using BinOpeInfo = std::map>; + using BinOpeInfo = std::map>; - PrecedenceClimbing(const std::shared_ptr &atom, - const std::shared_ptr &binop, const BinOpeInfo &info, - const Definition &rule) - : atom_(atom), binop_(binop), info_(info), rule_(rule) {} + PrecedenceClimbing(const std::shared_ptr &atom, + const std::shared_ptr &binop, const BinOpeInfo &info, + const Definition &rule) + : atom_(atom), binop_(binop), info_(info), rule_(rule) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - return parse_expression(s, n, vs, c, dt, 0); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + return parse_expression(s, n, vs, c, dt, 0); + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr atom_; - std::shared_ptr binop_; - BinOpeInfo info_; - const Definition &rule_; + std::shared_ptr atom_; + std::shared_ptr binop_; + BinOpeInfo info_; + const Definition &rule_; private: - size_t parse_expression(const char *s, size_t n, SemanticValues &vs, - Context &c, std::any &dt, size_t min_prec) const; + size_t parse_expression(const char *s, size_t n, SemanticValues &vs, + Context &c, std::any &dt, size_t min_prec) const; - Definition &get_reference_for_binop(Context &c) const; + Definition &get_reference_for_binop(Context &c) const; }; class Recovery : public Ope { public: - Recovery(const std::shared_ptr &ope) : ope_(ope) {} + Recovery(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class Cut : public Ope, public std::enable_shared_from_this { public: - size_t parse_core(const char * /*s*/, size_t /*n*/, SemanticValues & /*vs*/, - Context &c, std::any & /*dt*/) const override { - if (!c.cut_stack.empty()) { c.cut_stack.back() = true; } - return 0; - } + size_t parse_core(const char * /*s*/, size_t /*n*/, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + if (!c.cut_stack.empty()) { c.cut_stack.back() = true; } + return 0; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; }; /* * Factories */ template std::shared_ptr seq(Args &&...args) { - return std::make_shared(static_cast>(args)...); + return std::make_shared(static_cast>(args)...); } template std::shared_ptr cho(Args &&...args) { - return std::make_shared( - false, static_cast>(args)...); + return std::make_shared( + false, static_cast>(args)...); } template std::shared_ptr cho4label_(Args &&...args) { - return std::make_shared( - true, static_cast>(args)...); + return std::make_shared( + true, static_cast>(args)...); } inline std::shared_ptr zom(const std::shared_ptr &ope) { - return Repetition::zom(ope); + return Repetition::zom(ope); } inline std::shared_ptr oom(const std::shared_ptr &ope) { - return Repetition::oom(ope); + return Repetition::oom(ope); } inline std::shared_ptr opt(const std::shared_ptr &ope) { - return Repetition::opt(ope); + return Repetition::opt(ope); } inline std::shared_ptr rep(const std::shared_ptr &ope, size_t min, size_t max) { - return std::make_shared(ope, min, max); + return std::make_shared(ope, min, max); } inline std::shared_ptr apd(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr npd(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr dic(const std::vector &v, bool ignore_case) { - return std::make_shared(v, ignore_case); + return std::make_shared(v, ignore_case); } inline std::shared_ptr lit(std::string &&s) { - return std::make_shared(s, false); + return std::make_shared(s, false); } inline std::shared_ptr liti(std::string &&s) { - return std::make_shared(s, true); + return std::make_shared(s, true); } inline std::shared_ptr cls(const std::string &s) { - return std::make_shared(s, false, false); + return std::make_shared(s, false, false); } inline std::shared_ptr cls(const std::vector> &ranges, bool ignore_case = false) { - return std::make_shared(ranges, false, ignore_case); + return std::make_shared(ranges, false, ignore_case); } inline std::shared_ptr ncls(const std::string &s) { - return std::make_shared(s, true, false); + return std::make_shared(s, true, false); } inline std::shared_ptr ncls(const std::vector> &ranges, bool ignore_case = false) { - return std::make_shared(ranges, true, ignore_case); + return std::make_shared(ranges, true, ignore_case); } -inline std::shared_ptr chr(char dt) { - return std::make_shared(dt); +inline std::shared_ptr chr(char32_t dt) { + return std::make_shared(dt); } inline std::shared_ptr dot() { return std::make_shared(); } inline std::shared_ptr csc(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr cap(const std::shared_ptr &ope, Capture::MatchAction ma) { - return std::make_shared(ope, ma); + return std::make_shared(ope, ma); } inline std::shared_ptr tok(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr ign(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr usr(std::function fn) { - return std::make_shared(fn); + return std::make_shared(fn); } inline std::shared_ptr ref(const Grammar &grammar, const std::string &name, const char *s, bool is_macro, const std::vector> &args) { - return std::make_shared(grammar, name, s, is_macro, args); + return std::make_shared(grammar, name, s, is_macro, args); } inline std::shared_ptr wsp(const std::shared_ptr &ope) { - return std::make_shared(std::make_shared(ope)); + return std::make_shared(std::make_shared(ope)); } inline std::shared_ptr bkr(std::string &&name) { - return std::make_shared(name); + return std::make_shared(name); } inline std::shared_ptr pre(const std::shared_ptr &atom, const std::shared_ptr &binop, const PrecedenceClimbing::BinOpeInfo &info, const Definition &rule) { - return std::make_shared(atom, binop, info, rule); + return std::make_shared(atom, binop, info, rule); } inline std::shared_ptr rec(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr cut() { return std::make_shared(); } @@ -1686,511 +1966,600 @@ inline std::shared_ptr cut() { return std::make_shared(); } * Visitor */ struct Ope::Visitor { - virtual ~Visitor() {} - virtual void visit(Sequence &) {} - virtual void visit(PrioritizedChoice &) {} - virtual void visit(Repetition &) {} - virtual void visit(AndPredicate &) {} - virtual void visit(NotPredicate &) {} - virtual void visit(Dictionary &) {} - virtual void visit(LiteralString &) {} - virtual void visit(CharacterClass &) {} - virtual void visit(Character &) {} - virtual void visit(AnyCharacter &) {} - virtual void visit(CaptureScope &) {} - virtual void visit(Capture &) {} - virtual void visit(TokenBoundary &) {} - virtual void visit(Ignore &) {} - virtual void visit(User &) {} - virtual void visit(WeakHolder &) {} - virtual void visit(Holder &) {} - virtual void visit(Reference &) {} - virtual void visit(Whitespace &) {} - virtual void visit(BackReference &) {} - virtual void visit(PrecedenceClimbing &) {} - virtual void visit(Recovery &) {} - virtual void visit(Cut &) {} + virtual ~Visitor() {} + virtual void visit(Sequence &) {} + virtual void visit(PrioritizedChoice &) {} + virtual void visit(Repetition &) {} + virtual void visit(AndPredicate &) {} + virtual void visit(NotPredicate &) {} + virtual void visit(Dictionary &) {} + virtual void visit(LiteralString &) {} + virtual void visit(CharacterClass &) {} + virtual void visit(Character &) {} + virtual void visit(AnyCharacter &) {} + virtual void visit(CaptureScope &) {} + virtual void visit(Capture &) {} + virtual void visit(TokenBoundary &) {} + virtual void visit(Ignore &) {} + virtual void visit(User &) {} + virtual void visit(WeakHolder &) {} + virtual void visit(Holder &) {} + virtual void visit(Reference &) {} + virtual void visit(Whitespace &) {} + virtual void visit(BackReference &) {} + virtual void visit(PrecedenceClimbing &) {} + virtual void visit(Recovery &) {} + virtual void visit(Cut &) {} +}; + +struct TraversalVisitor : public Ope::Visitor { + using Ope::Visitor::visit; + void visit(Sequence &ope) override { + for (auto &op : ope.opes_) { + op->accept(*this); + } + } + void visit(PrioritizedChoice &ope) override { + for (auto &op : ope.opes_) { + op->accept(*this); + } + } + void visit(Repetition &ope) override { ope.ope_->accept(*this); } + void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } + void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } + void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } + void visit(Capture &ope) override { ope.ope_->accept(*this); } + void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } + void visit(Ignore &ope) override { ope.ope_->accept(*this); } + void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } + void visit(Holder &ope) override { ope.ope_->accept(*this); } + void visit(Whitespace &ope) override { ope.ope_->accept(*this); } + void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } }; struct TraceOpeName : public Ope::Visitor { - using Ope::Visitor::visit; + using Ope::Visitor::visit; - void visit(Sequence &) override { name_ = "Sequence"; } - void visit(PrioritizedChoice &) override { name_ = "PrioritizedChoice"; } - void visit(Repetition &) override { name_ = "Repetition"; } - void visit(AndPredicate &) override { name_ = "AndPredicate"; } - void visit(NotPredicate &) override { name_ = "NotPredicate"; } - void visit(Dictionary &) override { name_ = "Dictionary"; } - void visit(LiteralString &) override { name_ = "LiteralString"; } - void visit(CharacterClass &) override { name_ = "CharacterClass"; } - void visit(Character &) override { name_ = "Character"; } - void visit(AnyCharacter &) override { name_ = "AnyCharacter"; } - void visit(CaptureScope &) override { name_ = "CaptureScope"; } - void visit(Capture &) override { name_ = "Capture"; } - void visit(TokenBoundary &) override { name_ = "TokenBoundary"; } - void visit(Ignore &) override { name_ = "Ignore"; } - void visit(User &) override { name_ = "User"; } - void visit(WeakHolder &) override { name_ = "WeakHolder"; } - void visit(Holder &ope) override { name_ = ope.trace_name().data(); } - void visit(Reference &) override { name_ = "Reference"; } - void visit(Whitespace &) override { name_ = "Whitespace"; } - void visit(BackReference &) override { name_ = "BackReference"; } - void visit(PrecedenceClimbing &) override { name_ = "PrecedenceClimbing"; } - void visit(Recovery &) override { name_ = "Recovery"; } - void visit(Cut &) override { name_ = "Cut"; } + void visit(Sequence &) override { name_ = "Sequence"; } + void visit(PrioritizedChoice &) override { name_ = "PrioritizedChoice"; } + void visit(Repetition &) override { name_ = "Repetition"; } + void visit(AndPredicate &) override { name_ = "AndPredicate"; } + void visit(NotPredicate &) override { name_ = "NotPredicate"; } + void visit(Dictionary &) override { name_ = "Dictionary"; } + void visit(LiteralString &) override { name_ = "LiteralString"; } + void visit(CharacterClass &) override { name_ = "CharacterClass"; } + void visit(Character &) override { name_ = "Character"; } + void visit(AnyCharacter &) override { name_ = "AnyCharacter"; } + void visit(CaptureScope &) override { name_ = "CaptureScope"; } + void visit(Capture &) override { name_ = "Capture"; } + void visit(TokenBoundary &) override { name_ = "TokenBoundary"; } + void visit(Ignore &) override { name_ = "Ignore"; } + void visit(User &) override { name_ = "User"; } + void visit(WeakHolder &) override { name_ = "WeakHolder"; } + void visit(Holder &ope) override { name_ = ope.trace_name().data(); } + void visit(Reference &) override { name_ = "Reference"; } + void visit(Whitespace &) override { name_ = "Whitespace"; } + void visit(BackReference &) override { name_ = "BackReference"; } + void visit(PrecedenceClimbing &) override { name_ = "PrecedenceClimbing"; } + void visit(Recovery &) override { name_ = "Recovery"; } + void visit(Cut &) override { name_ = "Cut"; } - static std::string get(Ope &ope) { - TraceOpeName vis; - ope.accept(vis); - return vis.name_; - } + static std::string get(Ope &ope) { + TraceOpeName vis; + ope.accept(vis); + return vis.name_; + } private: - const char *name_ = nullptr; + const char *name_ = nullptr; }; -struct AssignIDToDefinition : public Ope::Visitor { - using Ope::Visitor::visit; +struct AssignIDToDefinition : public TraversalVisitor { + using TraversalVisitor::visit; - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(Repetition &ope) override { ope.ope_->accept(*this); } - void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } - void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override; - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override; - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(Holder &ope) override; + void visit(Reference &ope) override; + void visit(PrecedenceClimbing &ope) override; - std::unordered_map ids; + std::unordered_map ids; }; struct IsLiteralToken : public Ope::Visitor { - using Ope::Visitor::visit; + using Ope::Visitor::visit; - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - if (!IsLiteralToken::check(*op)) { return; } - } - result_ = true; + void visit(PrioritizedChoice &ope) override { + for (const auto &op : ope.opes_) { + if (!IsLiteralToken::check(*op)) { return; } } + result_ = true; + } - void visit(Dictionary &) override { result_ = true; } - void visit(LiteralString &) override { result_ = true; } + void visit(Dictionary &) override { result_ = true; } + void visit(LiteralString &) override { result_ = true; } - static bool check(Ope &ope) { - IsLiteralToken vis; - ope.accept(vis); - return vis.result_; - } + static bool check(Ope &ope) { + IsLiteralToken vis; + ope.accept(vis); + return vis.result_; + } private: - bool result_ = false; + bool result_ = false; }; -struct TokenChecker : public Ope::Visitor { - using Ope::Visitor::visit; +struct TokenChecker : public TraversalVisitor { + using TraversalVisitor::visit; - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(Repetition &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &) override { has_token_boundary_ = true; } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &) override { has_rule_ = true; } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(TokenBoundary &) override { has_token_boundary_ = true; } + void visit(AndPredicate &) override {} + void visit(NotPredicate &) override {} + void visit(WeakHolder &) override { has_rule_ = true; } + void visit(Reference &ope) override; - static bool is_token(Ope &ope) { - if (IsLiteralToken::check(ope)) { return true; } + static bool is_token(Ope &ope) { + if (IsLiteralToken::check(ope)) { return true; } - TokenChecker vis; - ope.accept(vis); - return vis.has_token_boundary_ || !vis.has_rule_; - } + TokenChecker vis; + ope.accept(vis); + return vis.has_token_boundary_ || !vis.has_rule_; + } private: - bool has_token_boundary_ = false; - bool has_rule_ = false; + bool has_token_boundary_ = false; + bool has_rule_ = false; }; struct FindLiteralToken : public Ope::Visitor { - using Ope::Visitor::visit; + using Ope::Visitor::visit; - void visit(LiteralString &ope) override { token_ = ope.lit_.data(); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(LiteralString &ope) override { token_ = ope.lit_.data(); } + void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } + void visit(Ignore &ope) override { ope.ope_->accept(*this); } + void visit(Reference &ope) override; + void visit(Recovery &ope) override { ope.ope_->accept(*this); } - static const char *token(Ope &ope) { - FindLiteralToken vis; - ope.accept(vis); - return vis.token_; - } + static const char *token(Ope &ope) { + FindLiteralToken vis; + ope.accept(vis); + return vis.token_; + } private: - const char *token_ = nullptr; + const char *token_ = nullptr; }; -struct DetectLeftRecursion : public Ope::Visitor { - using Ope::Visitor::visit; +struct DetectLeftRecursion : public TraversalVisitor { + using TraversalVisitor::visit; - DetectLeftRecursion(const std::string &name) : name_(name) {} + DetectLeftRecursion(const std::string &name) : name_(name) {} - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (done_) { - break; - } else if (error_s) { - done_ = true; - break; - } - } + void visit(Sequence &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (done_) { + break; + } else if (error_s) { + done_ = true; + break; + } } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (error_s) { - done_ = true; - break; - } - } + } + void visit(PrioritizedChoice &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (error_s) { + done_ = true; + break; + } } - void visit(Repetition &ope) override { - ope.ope_->accept(*this); - done_ = ope.min_ > 0; - } - void visit(AndPredicate &ope) override { - ope.ope_->accept(*this); - done_ = false; - } - void visit(NotPredicate &ope) override { - ope.ope_->accept(*this); - done_ = false; - } - void visit(Dictionary &) override { done_ = true; } - void visit(LiteralString &ope) override { done_ = !ope.lit_.empty(); } - void visit(CharacterClass &) override { done_ = true; } - void visit(Character &) override { done_ = true; } - void visit(AnyCharacter &) override { done_ = true; } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(User &) override { done_ = true; } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(BackReference &) override { done_ = true; } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } - void visit(Cut &) override { done_ = true; } + } + void visit(Repetition &ope) override { + ope.ope_->accept(*this); + done_ = ope.min_ > 0; + } + void visit(AndPredicate &ope) override { + ope.ope_->accept(*this); + done_ = false; + } + void visit(NotPredicate &ope) override { + ope.ope_->accept(*this); + done_ = false; + } + void visit(Dictionary &) override { done_ = true; } + void visit(LiteralString &ope) override { done_ = !ope.lit_.empty(); } + void visit(CharacterClass &) override { done_ = true; } + void visit(Character &) override { done_ = true; } + void visit(AnyCharacter &) override { done_ = true; } + void visit(User &) override { done_ = true; } + void visit(Reference &ope) override; + void visit(BackReference &) override { done_ = true; } + void visit(Cut &) override { done_ = true; } - const char *error_s = nullptr; + const char *error_s = nullptr; + + std::shared_ptr resolve_macro_arg(size_t iarg) const; private: - std::string name_; - std::unordered_set refs_; - bool done_ = false; + std::string name_; + std::unordered_set refs_; + bool done_ = false; + std::vector> *> macro_args_stack_; }; -struct HasEmptyElement : public Ope::Visitor { - using Ope::Visitor::visit; +struct ComputeCanBeEmpty : public TraversalVisitor { + using TraversalVisitor::visit; - HasEmptyElement(std::vector> &refs, - std::unordered_map &has_error_cache) - : refs_(refs), has_error_cache_(has_error_cache) {} + bool result = false; - void visit(Sequence &ope) override; - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (is_empty) { return; } - } - } - void visit(Repetition &ope) override { - if (ope.min_ == 0) { - set_error(); - } else { - ope.ope_->accept(*this); - } - } - void visit(AndPredicate &) override { set_error(); } - void visit(NotPredicate &) override { set_error(); } - void visit(LiteralString &ope) override { - if (ope.lit_.empty()) { set_error(); } - } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } - - bool is_empty = false; - const char *error_s = nullptr; - std::string error_name; - -private: - void set_error() { - is_empty = true; - tie(error_s, error_name) = refs_.back(); - } - std::vector> &refs_; - std::unordered_map &has_error_cache_; + void visit(Sequence &ope) override { + result = std::all_of(ope.opes_.begin(), ope.opes_.end(), [](auto &op) { + ComputeCanBeEmpty vis; + op->accept(vis); + return vis.result; + }); + } + void visit(PrioritizedChoice &ope) override { + result = std::any_of(ope.opes_.begin(), ope.opes_.end(), [](auto &op) { + ComputeCanBeEmpty vis; + op->accept(vis); + return vis.result; + }); + } + void visit(Repetition &ope) override { result = ope.min_ == 0; } + void visit(AndPredicate &) override { result = true; } + void visit(NotPredicate &) override { result = true; } + void visit(Dictionary &) override { result = false; } + void visit(LiteralString &ope) override { result = ope.lit_.empty(); } + void visit(CharacterClass &) override { result = false; } + void visit(Character &) override { result = false; } + void visit(AnyCharacter &) override { result = false; } + void visit(User &) override { result = false; } + void visit(Reference &ope) override; + void visit(BackReference &) override { result = false; } + void visit(Cut &) override { result = false; } }; -struct DetectInfiniteLoop : public Ope::Visitor { - using Ope::Visitor::visit; +struct HasEmptyElement : public TraversalVisitor { + using TraversalVisitor::visit; - DetectInfiniteLoop(const char *s, const std::string &name, - std::vector> &refs, - std::unordered_map &has_error_cache) - : refs_(refs), has_error_cache_(has_error_cache) { - refs_.emplace_back(s, name); - } + HasEmptyElement(std::vector> &refs, + std::unordered_map &has_error_cache) + : refs_(refs), has_error_cache_(has_error_cache) {} - DetectInfiniteLoop(std::vector> &refs, - std::unordered_map &has_error_cache) - : refs_(refs), has_error_cache_(has_error_cache) {} + void visit(Sequence &ope) override; + void visit(PrioritizedChoice &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (is_empty) { return; } + } + } + void visit(Repetition &ope) override { + if (ope.min_ == 0) { + set_error(); + } else { + ope.ope_->accept(*this); + } + } + void visit(AndPredicate &) override { set_error(); } + void visit(NotPredicate &) override { set_error(); } + void visit(LiteralString &ope) override { + if (ope.lit_.empty()) { set_error(); } + } + void visit(Reference &ope) override; - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (has_error) { return; } - } - } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (has_error) { return; } - } - } - void visit(Repetition &ope) override { - if (ope.max_ == std::numeric_limits::max()) { - HasEmptyElement vis(refs_, has_error_cache_); - ope.ope_->accept(vis); - if (vis.is_empty) { - has_error = true; - error_s = vis.error_s; - error_name = vis.error_name; - } - } else { - ope.ope_->accept(*this); - } - } - void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } - void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } - - bool has_error = false; - const char *error_s = nullptr; - std::string error_name; + bool is_empty = false; + const char *error_s = nullptr; + std::string error_name; private: - std::vector> &refs_; - std::unordered_map &has_error_cache_; + void set_error() { + is_empty = true; + tie(error_s, error_name) = refs_.back(); + } + std::vector> &refs_; + std::unordered_map &has_error_cache_; }; -struct ReferenceChecker : public Ope::Visitor { - using Ope::Visitor::visit; +struct DetectInfiniteLoop : public TraversalVisitor { + using TraversalVisitor::visit; - ReferenceChecker(const Grammar &grammar, - const std::vector ¶ms) - : grammar_(grammar), params_(params) {} + DetectInfiniteLoop(const char *s, const std::string &name, + std::vector> &refs, + std::unordered_map &has_error_cache) + : refs_(refs), has_error_cache_(has_error_cache) { + refs_.emplace_back(s, name); + } - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } + DetectInfiniteLoop(std::vector> &refs, + std::unordered_map &has_error_cache) + : refs_(refs), has_error_cache_(has_error_cache) {} + + void visit(Sequence &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (has_error) { return; } } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } + } + void visit(PrioritizedChoice &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (has_error) { return; } } - void visit(Repetition &ope) override { ope.ope_->accept(*this); } - void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } - void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + } + void visit(Repetition &ope) override { + if (ope.max_ == std::numeric_limits::max()) { + HasEmptyElement vis(refs_, has_error_cache_); + ope.ope_->accept(vis); + if (vis.is_empty) { + has_error = true; + error_s = vis.error_s; + error_name = vis.error_name; + } + } else { + ope.ope_->accept(*this); + } + } + void visit(Reference &ope) override; - std::unordered_map error_s; - std::unordered_map error_message; - std::unordered_set referenced; + bool has_error = false; + const char *error_s = nullptr; + std::string error_name; private: - const Grammar &grammar_; - const std::vector ¶ms_; + std::vector> &refs_; + std::unordered_map &has_error_cache_; }; -struct LinkReferences : public Ope::Visitor { - using Ope::Visitor::visit; +struct ReferenceChecker : public TraversalVisitor { + using TraversalVisitor::visit; - LinkReferences(Grammar &grammar, const std::vector ¶ms) - : grammar_(grammar), params_(params) {} + ReferenceChecker(const Grammar &grammar, + const std::vector ¶ms) + : grammar_(grammar), params_(params) {} - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(Repetition &ope) override { ope.ope_->accept(*this); } - void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } - void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(Reference &ope) override; + + std::unordered_map error_s; + std::unordered_map error_message; + std::unordered_set referenced; private: - Grammar &grammar_; - const std::vector ¶ms_; + const Grammar &grammar_; + const std::vector ¶ms_; +}; + +struct LinkReferences : public TraversalVisitor { + using TraversalVisitor::visit; + + LinkReferences(Grammar &grammar, const std::vector ¶ms) + : grammar_(grammar), params_(params) {} + + void visit(Reference &ope) override; + +private: + Grammar &grammar_; + const std::vector ¶ms_; }; struct FindReference : public Ope::Visitor { - using Ope::Visitor::visit; + using Ope::Visitor::visit; - FindReference(const std::vector> &args, - const std::vector ¶ms) - : args_(args), params_(params) {} + FindReference(const std::vector> &args, + const std::vector ¶ms) + : args_(args), params_(params) {} - void visit(Sequence &ope) override { - std::vector> opes; - for (auto o : ope.opes_) { - o->accept(*this); - opes.push_back(found_ope); - } - found_ope = std::make_shared(opes); + void visit(Sequence &ope) override { + std::vector> opes; + for (const auto &o : ope.opes_) { + o->accept(*this); + opes.emplace_back(std::move(found_ope)); } - void visit(PrioritizedChoice &ope) override { - std::vector> opes; - for (auto o : ope.opes_) { - o->accept(*this); - opes.push_back(found_ope); - } - found_ope = std::make_shared(opes); + found_ope = std::make_shared(opes); + } + void visit(PrioritizedChoice &ope) override { + std::vector> opes; + for (const auto &o : ope.opes_) { + o->accept(*this); + opes.emplace_back(std::move(found_ope)); } - void visit(Repetition &ope) override { - ope.ope_->accept(*this); - found_ope = rep(found_ope, ope.min_, ope.max_); - } - void visit(AndPredicate &ope) override { - ope.ope_->accept(*this); - found_ope = apd(found_ope); - } - void visit(NotPredicate &ope) override { - ope.ope_->accept(*this); - found_ope = npd(found_ope); - } - void visit(Dictionary &ope) override { found_ope = ope.shared_from_this(); } - void visit(LiteralString &ope) override { - found_ope = ope.shared_from_this(); - } - void visit(CharacterClass &ope) override { - found_ope = ope.shared_from_this(); - } - void visit(Character &ope) override { found_ope = ope.shared_from_this(); } - void visit(AnyCharacter &ope) override { found_ope = ope.shared_from_this(); } - void visit(CaptureScope &ope) override { - ope.ope_->accept(*this); - found_ope = csc(found_ope); - } - void visit(Capture &ope) override { - ope.ope_->accept(*this); - found_ope = cap(found_ope, ope.match_action_); - } - void visit(TokenBoundary &ope) override { - ope.ope_->accept(*this); - found_ope = tok(found_ope); - } - void visit(Ignore &ope) override { - ope.ope_->accept(*this); - found_ope = ign(found_ope); - } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { - ope.ope_->accept(*this); - found_ope = wsp(found_ope); - } - void visit(PrecedenceClimbing &ope) override { - ope.atom_->accept(*this); - found_ope = csc(found_ope); - } - void visit(Recovery &ope) override { - ope.ope_->accept(*this); - found_ope = rec(found_ope); - } - void visit(Cut &ope) override { found_ope = ope.shared_from_this(); } + found_ope = std::make_shared(opes); + } + void visit(Repetition &ope) override { + ope.ope_->accept(*this); + found_ope = rep(found_ope, ope.min_, ope.max_); + } + void visit(AndPredicate &ope) override { + ope.ope_->accept(*this); + found_ope = apd(found_ope); + } + void visit(NotPredicate &ope) override { + ope.ope_->accept(*this); + found_ope = npd(found_ope); + } + void visit(Dictionary &ope) override { found_ope = ope.shared_from_this(); } + void visit(LiteralString &ope) override { + found_ope = ope.shared_from_this(); + } + void visit(CharacterClass &ope) override { + found_ope = ope.shared_from_this(); + } + void visit(Character &ope) override { found_ope = ope.shared_from_this(); } + void visit(AnyCharacter &ope) override { found_ope = ope.shared_from_this(); } + void visit(CaptureScope &ope) override { + ope.ope_->accept(*this); + found_ope = csc(found_ope); + } + void visit(Capture &ope) override { + ope.ope_->accept(*this); + found_ope = cap(found_ope, ope.match_action_); + } + void visit(TokenBoundary &ope) override { + ope.ope_->accept(*this); + found_ope = tok(found_ope); + } + void visit(Ignore &ope) override { + ope.ope_->accept(*this); + found_ope = ign(found_ope); + } + void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } + void visit(Holder &ope) override { ope.ope_->accept(*this); } + void visit(Reference &ope) override; + void visit(Whitespace &ope) override { + ope.ope_->accept(*this); + found_ope = wsp(found_ope); + } + void visit(PrecedenceClimbing &ope) override { + ope.atom_->accept(*this); + found_ope = csc(found_ope); + } + void visit(Recovery &ope) override { + ope.ope_->accept(*this); + found_ope = rec(found_ope); + } + void visit(Cut &ope) override { found_ope = ope.shared_from_this(); } - std::shared_ptr found_ope; + std::shared_ptr found_ope; private: - const std::vector> &args_; - const std::vector ¶ms_; + const std::vector> &args_; + const std::vector ¶ms_; +}; + +/* + * First-Set computation + */ +struct ComputeFirstSet : public TraversalVisitor { + using TraversalVisitor::visit; + + void visit(Sequence &ope) override { + for (const auto &op : ope.opes_) { + auto save = result_; + result_ = FirstSet{}; + op->accept(*this); + auto element_fs = result_; + result_ = save; + result_.chars |= element_fs.chars; + if (element_fs.any_char) { result_.any_char = true; } + if (!result_.first_literal) { + result_.first_literal = element_fs.first_literal; + } + if (!result_.first_rule) { result_.first_rule = element_fs.first_rule; } + if (!element_fs.can_be_empty) { return; } + // This element can be empty, continue to next + } + result_.can_be_empty = true; + } + void visit(PrioritizedChoice &ope) override { + auto save = result_; + for (const auto &op : ope.opes_) { + result_ = FirstSet{}; + op->accept(*this); + save.merge(result_); + } + result_ = save; + } + void visit(Repetition &ope) override { + ope.ope_->accept(*this); + if (ope.min_ == 0) { result_.can_be_empty = true; } + } + void visit(AndPredicate &) override { result_.can_be_empty = true; } + void visit(NotPredicate &) override { result_.can_be_empty = true; } + void visit(Dictionary &ope) override { + for (const auto &[key, info] : ope.trie_.dic_) { + if (!key.empty()) { + auto ch = static_cast(key[0]); + result_.chars.set(ch); + if (ope.trie_.ignore_case_) { + result_.chars.set(static_cast(std::toupper(ch))); + result_.chars.set(static_cast(std::tolower(ch))); + } + } + } + } + void visit(LiteralString &ope) override { + if (ope.lit_.empty()) { + result_.can_be_empty = true; + } else { + auto ch = static_cast(ope.lit_[0]); + result_.chars.set(ch); + if (ope.ignore_case_) { + result_.chars.set(static_cast(std::toupper(ch))); + result_.chars.set(static_cast(std::tolower(ch))); + } + if (!result_.first_literal) { result_.first_literal = ope.lit_.c_str(); } + } + } + void visit(CharacterClass &ope) override { + for (const auto &range : ope.ranges_) { + auto cp1 = range.first; + auto cp2 = range.second; + if (cp1 > 0x7F || cp2 > 0x7F) { + // Non-ASCII range: conservative fallback + result_.any_char = true; + return; + } + for (auto cp = cp1; cp <= cp2; cp++) { + auto ch = static_cast(cp); + result_.chars.set(ch); + if (ope.ignore_case_) { + result_.chars.set(static_cast(std::toupper(ch))); + result_.chars.set(static_cast(std::tolower(ch))); + } + } + } + if (ope.negated_) { + result_.chars.flip(); + result_.any_char = true; // negated class can match non-ASCII + } + } + void visit(Character &ope) override { + if (ope.ch_ > 0x7F) { + result_.any_char = true; + } else { + result_.chars.set(static_cast(ope.ch_)); + } + } + void visit(AnyCharacter &) override { result_.any_char = true; } + void visit(User &) override { result_.any_char = true; } + void visit(Reference &ope) override; + void visit(BackReference &) override { result_.any_char = true; } + void visit(Cut &) override { result_.can_be_empty = true; } + + FirstSet result_; + +private: + std::unordered_set refs_; +}; + +struct SetupFirstSets : public TraversalVisitor { + using TraversalVisitor::visit; + + void visit(Sequence &ope) override; + void setup_keyword_guarded_identifier(Sequence &ope); + + void visit(PrioritizedChoice &ope) override { + ope.first_sets_.clear(); + ope.first_sets_.reserve(ope.opes_.size()); + for (const auto &op : ope.opes_) { + ComputeFirstSet cfs; + op->accept(cfs); + ope.first_sets_.push_back(cfs.result_); + } + for (const auto &op : ope.opes_) { + op->accept(*this); + } + } + void visit(Repetition &ope) override { + ope.ope_->accept(*this); + // ISpan optimization: detect Repetition + ASCII CharacterClass + auto cc = dynamic_cast(ope.ope_.get()); + if (cc && cc->is_ascii_only()) { ope.span_bitset_ = &cc->ascii_bitset(); } + } + void visit(Reference &ope) override; + +private: + std::unordered_set refs_; }; /* @@ -2205,279 +2574,299 @@ static const char *RECOVER_DEFINITION_NAME = "%recover"; */ class Definition { public: - struct Result { - bool ret; - bool recovered; - size_t len; - ErrorInfo error_info; - }; + struct Result { + bool ret; + bool recovered; + size_t len; + ErrorInfo error_info; + }; - Definition() : holder_(std::make_shared(this)) {} + Definition() : holder_(std::make_shared(this)) {} - Definition(const Definition &rhs) : name(rhs.name), holder_(rhs.holder_) { - holder_->outer_ = this; + Definition(const Definition &rhs) : name(rhs.name), holder_(rhs.holder_) { + holder_->outer_ = this; + } + + Definition(const std::shared_ptr &ope) + : holder_(std::make_shared(this)) { + *this <= ope; + } + + operator std::shared_ptr() { + return std::make_shared(holder_); + } + + Definition &operator<=(const std::shared_ptr &ope) { + holder_->ope_ = ope; + return *this; + } + + Result parse(const char *s, size_t n, const char *path = nullptr, + Log log = nullptr) const { + SemanticValues vs; + std::any dt; + return parse_core(s, n, vs, dt, path, log); + } + + Result parse(const char *s, const char *path = nullptr, + Log log = nullptr) const { + auto n = strlen(s); + return parse(s, n, path, log); + } + + Result parse(const char *s, size_t n, std::any &dt, + const char *path = nullptr, Log log = nullptr) const { + SemanticValues vs; + return parse_core(s, n, vs, dt, path, log); + } + + Result parse(const char *s, std::any &dt, const char *path = nullptr, + Log log = nullptr) const { + auto n = strlen(s); + return parse(s, n, dt, path, log); + } + + template + Result parse_and_get_value(const char *s, size_t n, T &val, + const char *path = nullptr, + Log log = nullptr) const { + SemanticValues vs; + std::any dt; + auto r = parse_core(s, n, vs, dt, path, log); + if (r.ret && !vs.empty() && vs.front().has_value()) { + val = std::any_cast(vs[0]); } + return r; + } - Definition(const std::shared_ptr &ope) - : holder_(std::make_shared(this)) { - *this <= ope; - } + template + Result parse_and_get_value(const char *s, T &val, const char *path = nullptr, + Log log = nullptr) const { + auto n = strlen(s); + return parse_and_get_value(s, n, val, path, log); + } - operator std::shared_ptr() { - return std::make_shared(holder_); + template + Result parse_and_get_value(const char *s, size_t n, std::any &dt, T &val, + const char *path = nullptr, + Log log = nullptr) const { + SemanticValues vs; + auto r = parse_core(s, n, vs, dt, path, log); + if (r.ret && !vs.empty() && vs.front().has_value()) { + val = std::any_cast(vs[0]); } + return r; + } - Definition &operator<=(const std::shared_ptr &ope) { - holder_->ope_ = ope; - return *this; - } - - Result parse(const char *s, size_t n, const char *path = nullptr, - Log log = nullptr) const { - SemanticValues vs; - std::any dt; - return parse_core(s, n, vs, dt, path, log); - } - - Result parse(const char *s, const char *path = nullptr, - Log log = nullptr) const { - auto n = strlen(s); - return parse(s, n, path, log); - } - - Result parse(const char *s, size_t n, std::any &dt, - const char *path = nullptr, Log log = nullptr) const { - SemanticValues vs; - return parse_core(s, n, vs, dt, path, log); - } - - Result parse(const char *s, std::any &dt, const char *path = nullptr, - Log log = nullptr) const { - auto n = strlen(s); - return parse(s, n, dt, path, log); - } - - template - Result parse_and_get_value(const char *s, size_t n, T &val, - const char *path = nullptr, - Log log = nullptr) const { - SemanticValues vs; - std::any dt; - auto r = parse_core(s, n, vs, dt, path, log); - if (r.ret && !vs.empty() && vs.front().has_value()) { - val = std::any_cast(vs[0]); - } - return r; - } - - template - Result parse_and_get_value(const char *s, T &val, const char *path = nullptr, - Log log = nullptr) const { - auto n = strlen(s); - return parse_and_get_value(s, n, val, path, log); - } - - template - Result parse_and_get_value(const char *s, size_t n, std::any &dt, T &val, - const char *path = nullptr, - Log log = nullptr) const { - SemanticValues vs; - auto r = parse_core(s, n, vs, dt, path, log); - if (r.ret && !vs.empty() && vs.front().has_value()) { - val = std::any_cast(vs[0]); - } - return r; - } - - template - Result parse_and_get_value(const char *s, std::any &dt, T &val, - const char *path = nullptr, - Log log = nullptr) const { - auto n = strlen(s); - return parse_and_get_value(s, n, dt, val, path, log); - } + template + Result parse_and_get_value(const char *s, std::any &dt, T &val, + const char *path = nullptr, + Log log = nullptr) const { + auto n = strlen(s); + return parse_and_get_value(s, n, dt, val, path, log); + } #if defined(__cpp_lib_char8_t) - Result parse(const char8_t *s, size_t n, const char *path = nullptr, - Log log = nullptr) const { - return parse(reinterpret_cast(s), n, path, log); - } + Result parse(const char8_t *s, size_t n, const char *path = nullptr, + Log log = nullptr) const { + return parse(reinterpret_cast(s), n, path, log); + } - Result parse(const char8_t *s, const char *path = nullptr, - Log log = nullptr) const { - return parse(reinterpret_cast(s), path, log); - } + Result parse(const char8_t *s, const char *path = nullptr, + Log log = nullptr) const { + return parse(reinterpret_cast(s), path, log); + } - Result parse(const char8_t *s, size_t n, std::any &dt, - const char *path = nullptr, Log log = nullptr) const { - return parse(reinterpret_cast(s), n, dt, path, log); - } + Result parse(const char8_t *s, size_t n, std::any &dt, + const char *path = nullptr, Log log = nullptr) const { + return parse(reinterpret_cast(s), n, dt, path, log); + } - Result parse(const char8_t *s, std::any &dt, const char *path = nullptr, - Log log = nullptr) const { - return parse(reinterpret_cast(s), dt, path, log); - } + Result parse(const char8_t *s, std::any &dt, const char *path = nullptr, + Log log = nullptr) const { + return parse(reinterpret_cast(s), dt, path, log); + } - template - Result parse_and_get_value(const char8_t *s, size_t n, T &val, - const char *path = nullptr, - Log log = nullptr) const { - return parse_and_get_value(reinterpret_cast(s), n, val, *path, - log); - } + template + Result parse_and_get_value(const char8_t *s, size_t n, T &val, + const char *path = nullptr, + Log log = nullptr) const { + return parse_and_get_value(reinterpret_cast(s), n, val, path, + log); + } - template - Result parse_and_get_value(const char8_t *s, T &val, - const char *path = nullptr, - Log log = nullptr) const { - return parse_and_get_value(reinterpret_cast(s), val, *path, - log); - } + template + Result parse_and_get_value(const char8_t *s, T &val, + const char *path = nullptr, + Log log = nullptr) const { + return parse_and_get_value(reinterpret_cast(s), val, path, + log); + } - template - Result parse_and_get_value(const char8_t *s, size_t n, std::any &dt, T &val, - const char *path = nullptr, - Log log = nullptr) const { - return parse_and_get_value(reinterpret_cast(s), n, dt, val, - *path, log); - } + template + Result parse_and_get_value(const char8_t *s, size_t n, std::any &dt, T &val, + const char *path = nullptr, + Log log = nullptr) const { + return parse_and_get_value(reinterpret_cast(s), n, dt, val, + path, log); + } - template - Result parse_and_get_value(const char8_t *s, std::any &dt, T &val, - const char *path = nullptr, - Log log = nullptr) const { - return parse_and_get_value(reinterpret_cast(s), dt, val, - *path, log); - } + template + Result parse_and_get_value(const char8_t *s, std::any &dt, T &val, + const char *path = nullptr, + Log log = nullptr) const { + return parse_and_get_value(reinterpret_cast(s), dt, val, path, + log); + } #endif - void operator=(Action a) { action = a; } + void operator=(Action a) { action = a; } - template Definition &operator,(T fn) { - operator=(fn); - return *this; - } + template Definition &operator,(T fn) { + operator=(fn); + return *this; + } - Definition &operator~() { - ignoreSemanticValue = true; - return *this; - } + Definition &operator~() { + ignoreSemanticValue = true; + return *this; + } - void accept(Ope::Visitor &v) { holder_->accept(v); } + void accept(Ope::Visitor &v) { holder_->accept(v); } - std::shared_ptr get_core_operator() const { return holder_->ope_; } + std::shared_ptr get_core_operator() const { return holder_->ope_; } - bool is_token() const { - std::call_once(is_token_init_, [this]() { - is_token_ = TokenChecker::is_token(*get_core_operator()); - }); - return is_token_; - } + bool is_token() const { + std::call_once(is_token_init_, [this]() { + is_token_ = TokenChecker::is_token(*get_core_operator()); + }); + return is_token_; + } - std::string name; - const char *s_ = nullptr; - std::pair line_ = {1, 1}; + std::string name; + const char *s_ = nullptr; + std::pair line_ = {1, 1}; - std::function - predicate; + Predicate predicate; - size_t id = 0; - Action action; - std::function - enter; - std::function - leave; - bool ignoreSemanticValue = false; - std::shared_ptr whitespaceOpe; - std::shared_ptr wordOpe; - bool enablePackratParsing = false; - bool is_macro = false; - std::vector params; - bool disable_action = false; + size_t id = 0; + Action action; + std::function + enter; + std::function + leave; + bool ignoreSemanticValue = false; + std::shared_ptr whitespaceOpe; + std::shared_ptr wordOpe; + bool enablePackratParsing = false; + bool is_macro = false; + std::vector params; + bool disable_action = false; + bool is_left_recursive = false; + bool can_be_empty = false; - TracerEnter tracer_enter; - TracerLeave tracer_leave; - bool verbose_trace = false; - TracerStartOrEnd tracer_start; - TracerStartOrEnd tracer_end; + TracerEnter tracer_enter; + TracerLeave tracer_leave; + bool verbose_trace = false; + TracerStartOrEnd tracer_start; + TracerStartOrEnd tracer_end; - std::string error_message; - bool no_ast_opt = false; + std::string error_message; + bool no_ast_opt = false; - bool eoi_check = true; + bool eoi_check = true; + + // Per-rule packrat stats (optional, for profiling) + mutable bool collect_packrat_stats = false; + mutable std::vector packrat_stats_; private: - friend class Reference; - friend class ParserGenerator; + friend class Reference; + friend class ParserGenerator; - Definition &operator=(const Definition &rhs); - Definition &operator=(Definition &&rhs); + Definition &operator=(const Definition &rhs); + Definition &operator=(Definition &&rhs); - void initialize_definition_ids() const { - std::call_once(definition_ids_init_, [&]() { - AssignIDToDefinition vis; - holder_->accept(vis); - if (whitespaceOpe) { whitespaceOpe->accept(vis); } - if (wordOpe) { wordOpe->accept(vis); } - definition_ids_.swap(vis.ids); - }); + void initialize_definition_ids() const { + std::call_once(definition_ids_init_, [&]() { + AssignIDToDefinition vis; + holder_->accept(vis); + if (whitespaceOpe) { whitespaceOpe->accept(vis); } + if (wordOpe) { wordOpe->accept(vis); } + definition_ids_.swap(vis.ids); + }); + } + + void initialize_packrat_filter() const; + + Result parse_core(const char *s, size_t n, SemanticValues &vs, std::any &dt, + const char *path, Log log) const { + initialize_definition_ids(); + + std::shared_ptr ope = holder_; + + std::any trace_data; + if (tracer_start) { tracer_start(trace_data); } + auto se = scope_exit([&]() { + if (tracer_end) { tracer_end(trace_data); } + }); + + Context c(path, s, n, definition_ids_.size(), whitespaceOpe, wordOpe, + enablePackratParsing, tracer_enter, tracer_leave, trace_data, + verbose_trace, log); + + if (collect_packrat_stats) { + packrat_stats_.resize(definition_ids_.size()); + c.packrat_stats = &packrat_stats_; } - Result parse_core(const char *s, size_t n, SemanticValues &vs, std::any &dt, - const char *path, Log log) const { - initialize_definition_ids(); - - std::shared_ptr ope = holder_; - - std::any trace_data; - if (tracer_start) { tracer_start(trace_data); } - auto se1 = scope_exit([&]() { - if (tracer_end) { tracer_end(trace_data); } - }); - - Context c(path, s, n, definition_ids_.size(), whitespaceOpe, wordOpe, - enablePackratParsing, tracer_enter, tracer_leave, trace_data, - verbose_trace, log); - - size_t i = 0; - - if (whitespaceOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se2 = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - - auto len = whitespaceOpe->parse(s, n, vs, c, dt); - if (fail(len)) { return Result{false, c.recovered, i, c.error_info}; } - - i = len; - } - - auto len = ope->parse(s + i, n - i, vs, c, dt); - auto ret = success(len); - if (ret) { - i += len; - if (eoi_check) { - if (i < n) { - if (c.error_info.error_pos - c.s < s + i - c.s) { - c.error_info.message_pos = s + i; - c.error_info.message = "expected end of input"; - } - ret = false; - } - } - } - return Result{ret, c.recovered, i, c.error_info}; + if (enablePackratParsing) { + initialize_packrat_filter(); + if (!packrat_filter_.empty()) { + c.packrat_rule_filter = &packrat_filter_; + } } - std::shared_ptr holder_; - mutable std::once_flag is_token_init_; - mutable bool is_token_ = false; - mutable std::once_flag assign_id_to_definition_init_; - mutable std::once_flag definition_ids_init_; - mutable std::unordered_map definition_ids_; + size_t i = 0; + + if (whitespaceOpe) { + auto save_ignore_trace_state = c.ignore_trace_state; + c.ignore_trace_state = !c.verbose_trace; + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + + auto len = whitespaceOpe->parse(s, n, vs, c, dt); + if (fail(len)) { return Result{false, c.recovered, i, c.error_info}; } + + i = len; + } + + auto len = ope->parse(s + i, n - i, vs, c, dt); + auto ret = success(len); + if (ret) { + i += len; + if (eoi_check) { + if (i < n) { + if (c.error_info.error_pos - c.s < s + i - c.s) { + c.error_info.message_pos = s + i; + c.error_info.message = "expected end of input"; + } + ret = false; + } + } + } + return Result{ret, c.recovered, i, c.error_info}; + } + + std::shared_ptr holder_; + mutable std::once_flag is_token_init_; + mutable bool is_token_ = false; + mutable std::once_flag assign_id_to_definition_init_; + mutable std::once_flag definition_ids_init_; + mutable std::unordered_map definition_ids_; + mutable std::once_flag packrat_filter_init_; + mutable std::vector packrat_filter_; }; /* @@ -2487,592 +2876,704 @@ private: inline size_t parse_literal(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt, const std::string &lit, std::once_flag &init_is_word, bool &is_word, - bool ignore_case) { - size_t i = 0; - for (; i < lit.size(); i++) { - if (i >= n || (ignore_case ? (std::tolower(s[i]) != std::tolower(lit[i])) - : (s[i] != lit[i]))) { - c.set_error_pos(s, lit.data()); - return static_cast(-1); - } + bool ignore_case, const std::string &lower_lit) { + size_t i = 0; + for (; i < lit.size(); i++) { + if (i >= n || + (ignore_case ? (static_cast(std::tolower( + static_cast(s[i]))) != lower_lit[i]) + : (s[i] != lit[i]))) { + c.set_error_pos(s, lit.data()); + return static_cast(-1); } + } - // Word check - if (c.wordOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + // Word check + if (c.wordOpe) { + auto save_ignore_trace_state = c.ignore_trace_state; + c.ignore_trace_state = !c.verbose_trace; + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - std::call_once(init_is_word, [&]() { - SemanticValues dummy_vs; - Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, - nullptr, nullptr, false, nullptr); - std::any dummy_dt; + std::call_once(init_is_word, [&]() { + SemanticValues dummy_vs; + Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, + nullptr, nullptr, false, nullptr); + std::any dummy_dt; - auto len = - c.wordOpe->parse(lit.data(), lit.size(), dummy_vs, dummy_c, dummy_dt); - is_word = success(len); - }); + auto len = + c.wordOpe->parse(lit.data(), lit.size(), dummy_vs, dummy_c, dummy_dt); + is_word = success(len); + }); - if (is_word) { - SemanticValues dummy_vs; - Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, - nullptr, nullptr, false, nullptr); - std::any dummy_dt; + if (is_word) { + SemanticValues dummy_vs; + Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, + nullptr, nullptr, false, nullptr); + std::any dummy_dt; - NotPredicate ope(c.wordOpe); - auto len = ope.parse(s + i, n - i, dummy_vs, dummy_c, dummy_dt); - if (fail(len)) { - c.set_error_pos(s, lit.data()); - return len; - } - i += len; - } + NotPredicate ope(c.wordOpe); + auto len = ope.parse(s + i, n - i, dummy_vs, dummy_c, dummy_dt); + if (fail(len)) { + c.set_error_pos(s, lit.data()); + return len; + } + i += len; } + } - // Skip whitespace - if (!c.in_token_boundary_count && c.whitespaceOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + // Skip whitespace + auto wl = c.skip_whitespace(s + i, n - i, vs, dt); + if (fail(wl)) { return wl; } + i += wl; - auto len = c.whitespaceOpe->parse(s + i, n - i, vs, c, dt); - if (fail(len)) { return len; } - i += len; - } - - return i; + return i; } inline std::pair SemanticValues::line_info() const { - assert(c_); - return c_->line_info(sv_.data()); + assert(c_); + return c_->line_info(sv_.data()); } inline void ErrorInfo::output_log(const Log &log, const char *s, size_t n) { - if (message_pos) { - if (message_pos > last_output_pos) { - last_output_pos = message_pos; - auto line = line_info(s, message_pos); - std::string msg; - if (auto unexpected_token = heuristic_error_token(s, n, message_pos); - !unexpected_token.empty()) { - msg = replace_all(message, "%t", unexpected_token); + if (message_pos) { + if (message_pos > last_output_pos) { + last_output_pos = message_pos; + auto line = line_info(s, message_pos); + std::string msg; + if (auto unexpected_token = heuristic_error_token(s, n, message_pos); + !unexpected_token.empty()) { + msg = replace_all(message, "%t", unexpected_token); - auto unexpected_char = unexpected_token.substr( - 0, - codepoint_length(unexpected_token.data(), unexpected_token.size())); + auto unexpected_char = unexpected_token.substr( + 0, + codepoint_length(unexpected_token.data(), unexpected_token.size())); - msg = replace_all(msg, "%c", unexpected_char); - } else { - msg = message; - } - log(line.first, line.second, msg, label); - } - } else if (error_pos) { - if (error_pos > last_output_pos) { - last_output_pos = error_pos; - auto line = line_info(s, error_pos); - - std::string msg; - if (expected_tokens.empty()) { - msg = "syntax error."; - } else { - msg = "syntax error"; - - // unexpected token - if (auto unexpected_token = heuristic_error_token(s, n, error_pos); - !unexpected_token.empty()) { - msg += ", unexpected '"; - msg += unexpected_token; - msg += "'"; - } - - auto first_item = true; - size_t i = 0; - while (i < expected_tokens.size()) { - auto [error_literal, error_rule] = expected_tokens[i]; - - // Skip rules start with '_' - if (!(error_rule && error_rule->name[0] == '_')) { - msg += (first_item ? ", expecting " : ", "); - if (error_literal) { - msg += "'"; - msg += error_literal; - msg += "'"; - } else { - msg += "<" + error_rule->name + ">"; - if (label.empty()) { label = error_rule->name; } - } - first_item = false; - } - - i++; - } - msg += "."; - } - log(line.first, line.second, msg, label); - } + msg = replace_all(msg, "%c", unexpected_char); + } else { + msg = message; + } + log(line.first, line.second, msg, label); } + } else if (error_pos) { + if (error_pos > last_output_pos) { + last_output_pos = error_pos; + auto line = line_info(s, error_pos); + + std::string msg; + if (expected_tokens.empty()) { + msg = "syntax error."; + } else { + msg = "syntax error"; + + // unexpected token + if (auto unexpected_token = heuristic_error_token(s, n, error_pos); + !unexpected_token.empty()) { + msg += ", unexpected '"; + msg += unexpected_token; + msg += "'"; + } + + auto first_item = true; + size_t i = 0; + while (i < expected_tokens.size()) { + auto [error_literal, error_rule] = expected_tokens[i]; + + // Skip rules start with '_' + if (!(error_rule && error_rule->name[0] == '_')) { + msg += (first_item ? ", expecting " : ", "); + if (error_literal) { + msg += "'"; + msg += error_literal; + msg += "'"; + } else { + msg += "<" + error_rule->name + ">"; + if (label.empty()) { label = error_rule->name; } + } + first_item = false; + } + + i++; + } + msg += "."; + } + log(line.first, line.second, msg, label); + } + } +} + +inline size_t Context::skip_whitespace(const char *a_s, size_t n, + SemanticValues &vs, std::any &dt) { + if (in_token_boundary_count || !whitespaceOpe) { return 0; } + auto save = ignore_trace_state; + ignore_trace_state = !verbose_trace; + auto se = scope_exit([&]() { ignore_trace_state = save; }); + return whitespaceOpe->parse(a_s, n, vs, *this, dt); } inline void Context::set_error_pos(const char *a_s, const char *literal) { - if (log) { - if (error_info.error_pos <= a_s) { - if (error_info.error_pos < a_s || !error_info.keep_previous_token) { - error_info.error_pos = a_s; - error_info.expected_tokens.clear(); - } + if (log) { + if (error_info.error_pos <= a_s) { + if (error_info.error_pos < a_s || !error_info.keep_previous_token) { + error_info.error_pos = a_s; + error_info.expected_tokens.clear(); + } - const char *error_literal = nullptr; - const Definition *error_rule = nullptr; + const char *error_literal = nullptr; + const Definition *error_rule = nullptr; - if (literal) { - error_literal = literal; - } else if (!rule_stack.empty()) { - auto rule = rule_stack.back(); - auto ope = rule->get_core_operator(); - if (auto token = FindLiteralToken::token(*ope); - token && token[0] != '\0') { - error_literal = token; - } - } - - for (auto r : rule_stack) { - error_rule = r; - if (r->is_token()) { break; } - } - - if (error_literal || error_rule) { - error_info.add(error_literal, error_rule); - } + if (literal) { + error_literal = literal; + } else if (!rule_stack.empty()) { + auto rule = rule_stack.back(); + auto ope = rule->get_core_operator(); + if (auto token = FindLiteralToken::token(*ope); + token && token[0] != '\0') { + error_literal = token; } + } + + for (auto r : rule_stack) { + error_rule = r; + if (r->is_token()) { break; } + } + + if (error_literal || error_rule) { + error_info.add(error_literal, error_rule); + } } + } } inline void Context::trace_enter(const Ope &ope, const char *a_s, size_t n, const SemanticValues &vs, std::any &dt) { - trace_ids.push_back(next_trace_id++); - tracer_enter(ope, a_s, n, vs, *this, dt, trace_data); + trace_ids.push_back(next_trace_id++); + tracer_enter(ope, a_s, n, vs, *this, dt, trace_data); } inline void Context::trace_leave(const Ope &ope, const char *a_s, size_t n, const SemanticValues &vs, std::any &dt, size_t len) { - tracer_leave(ope, a_s, n, vs, *this, dt, len, trace_data); - trace_ids.pop_back(); + tracer_leave(ope, a_s, n, vs, *this, dt, len, trace_data); + trace_ids.pop_back(); } inline bool Context::is_traceable(const Ope &ope) const { - if (tracer_enter && tracer_leave) { - if (ignore_trace_state) { return false; } - return !dynamic_cast(&ope); - } - return false; + if (tracer_enter && tracer_leave) { + if (ignore_trace_state) { return false; } + return !dynamic_cast(&ope); + } + return false; } inline size_t Ope::parse(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - if (c.is_traceable(*this)) { - c.trace_enter(*this, s, n, vs, dt); - auto len = parse_core(s, n, vs, c, dt); - c.trace_leave(*this, s, n, vs, dt, len); - return len; - } - return parse_core(s, n, vs, c, dt); + if (c.is_traceable(*this)) { + c.trace_enter(*this, s, n, vs, dt); + auto len = parse_core(s, n, vs, c, dt); + c.trace_leave(*this, s, n, vs, dt, len); + return len; + } + return parse_core(s, n, vs, c, dt); } inline size_t Dictionary::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - size_t id; - auto i = trie_.match(s, n, id); + size_t id; + auto i = trie_.match(s, n, id); - if (i == 0) { + if (i == 0) { + c.set_error_pos(s); + return static_cast(-1); + } + + vs.choice_count_ = trie_.items_count(); + vs.choice_ = id; + + // Word check + if (c.wordOpe) { + auto save_ignore_trace_state = c.ignore_trace_state; + c.ignore_trace_state = !c.verbose_trace; + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + + { + SemanticValues dummy_vs; + Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, + nullptr, nullptr, false, nullptr); + std::any dummy_dt; + + NotPredicate ope(c.wordOpe); + auto len = ope.parse(s + i, n - i, dummy_vs, dummy_c, dummy_dt); + if (fail(len)) { c.set_error_pos(s); - return static_cast(-1); + return len; + } + i += len; } + } - vs.choice_count_ = trie_.size(); - vs.choice_ = id; + // Skip whitespace + auto wl = c.skip_whitespace(s + i, n - i, vs, dt); + if (fail(wl)) { return wl; } + i += wl; - // Word check - if (c.wordOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - - { - SemanticValues dummy_vs; - Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, - nullptr, nullptr, false, nullptr); - std::any dummy_dt; - - NotPredicate ope(c.wordOpe); - auto len = ope.parse(s + i, n - i, dummy_vs, dummy_c, dummy_dt); - if (fail(len)) { - c.set_error_pos(s); - return len; - } - i += len; - } - } - - // Skip whitespace - if (!c.in_token_boundary_count && c.whitespaceOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - - auto len = c.whitespaceOpe->parse(s + i, n - i, vs, c, dt); - if (fail(len)) { return len; } - i += len; - } - - return i; + return i; } inline size_t LiteralString::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - return parse_literal(s, n, vs, c, dt, lit_, init_is_word_, is_word_, - ignore_case_); + return parse_literal(s, n, vs, c, dt, lit_, init_is_word_, is_word_, + ignore_case_, lower_lit_); } inline size_t TokenBoundary::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se1 = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + auto save_ignore_trace_state = c.ignore_trace_state; + c.ignore_trace_state = !c.verbose_trace; + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - size_t len; - { - c.in_token_boundary_count++; - auto se2 = scope_exit([&]() { c.in_token_boundary_count--; }); - len = ope_->parse(s, n, vs, c, dt); - } + size_t len; + { + c.in_token_boundary_count++; + auto se = scope_exit([&]() { c.in_token_boundary_count--; }); + len = ope_->parse(s, n, vs, c, dt); + } - if (success(len)) { - vs.tokens.emplace_back(std::string_view(s, len)); + if (success(len)) { + vs.tokens.emplace_back(std::string_view(s, len)); - if (!c.in_token_boundary_count) { - if (c.whitespaceOpe) { - auto l = c.whitespaceOpe->parse(s + len, n - len, vs, c, dt); - if (fail(l)) { return l; } - len += l; - } - } - } - return len; + auto wl = c.skip_whitespace(s + len, n - len, vs, dt); + if (fail(wl)) { return wl; } + len += wl; + } + return len; } inline size_t Holder::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - if (!ope_) { - throw std::logic_error("Uninitialized definition ope was used..."); - } + if (!ope_) { + throw std::logic_error("Uninitialized definition ope was used..."); + } - // Macro reference - if (outer_->is_macro) { - c.rule_stack.push_back(outer_); - auto len = ope_->parse(s, n, vs, c, dt); - c.rule_stack.pop_back(); - return len; - } + // Macro reference + if (outer_->is_macro) { + c.rule_stack.push_back(outer_); + auto len = ope_->parse(s, n, vs, c, dt); + c.rule_stack.pop_back(); + return len; + } - size_t len; - std::any val; + size_t len; + std::any val; - c.packrat(s, outer_->id, len, val, [&](std::any &a_val) { - if (outer_->enter) { outer_->enter(c, s, n, dt); } - auto &chvs = c.push_semantic_values_scope(); - auto se = scope_exit([&]() { - c.pop_semantic_values_scope(); - if (outer_->leave) { outer_->leave(c, s, n, len, a_val, dt); } - }); + // Shared parse body: invokes enter/leave callbacks, parses the rule's + // operator, handles actions/predicates/errors, and calls reduce. + // Returns {parse_len, parse_val}. + auto do_parse = [&]() { + size_t parse_len; + std::any parse_val; - c.rule_stack.push_back(outer_); - len = ope_->parse(s, n, chvs, c, dt); - c.rule_stack.pop_back(); - - // Invoke action - if (success(len)) { - chvs.sv_ = std::string_view(s, len); - chvs.name_ = outer_->name; - - auto ope_ptr = ope_.get(); - { - auto tok_ptr = dynamic_cast(ope_ptr); - if (tok_ptr) { ope_ptr = tok_ptr->ope_.get(); } - } - if (!dynamic_cast(ope_ptr) && - !dynamic_cast(ope_ptr)) { - chvs.choice_count_ = 0; - chvs.choice_ = 0; - } - - std::string msg; - if (outer_->predicate && !outer_->predicate(chvs, dt, msg)) { - if (c.log && !msg.empty() && c.error_info.message_pos < s) { - c.error_info.message_pos = s; - c.error_info.message = msg; - c.error_info.label = outer_->name; - } - len = static_cast(-1); - } - - if (success(len)) { - if (!c.recovered) { a_val = reduce(chvs, dt); } - } else { - if (c.log && !msg.empty() && c.error_info.message_pos < s) { - c.error_info.message_pos = s; - c.error_info.message = msg; - c.error_info.label = outer_->name; - } - } - } else { - if (c.log && !outer_->error_message.empty() && - c.error_info.message_pos < s) { - c.error_info.message_pos = s; - c.error_info.message = outer_->error_message; - c.error_info.label = outer_->name; - } - } + if (outer_->enter) { outer_->enter(c, s, n, dt); } + auto &chvs = c.push_semantic_values_scope(); + auto se = scope_exit([&]() { + c.pop_semantic_values_scope(); + if (outer_->leave) { outer_->leave(c, s, n, parse_len, parse_val, dt); } }); - if (success(len)) { - if (!outer_->ignoreSemanticValue) { - vs.emplace_back(std::move(val)); - vs.tags.emplace_back(str2tag(outer_->name)); + c.rule_stack.push_back(outer_); + parse_len = ope_->parse(s, n, chvs, c, dt); + c.rule_stack.pop_back(); + + if (success(parse_len)) { + chvs.sv_ = std::string_view(s, parse_len); + chvs.name_ = outer_->name; + + auto ope_ptr = ope_.get(); + if (ope_ptr->is_token_boundary) { + ope_ptr = static_cast(ope_ptr)->ope_.get(); + } + if (!ope_ptr->is_choice_like) { + chvs.choice_count_ = 0; + chvs.choice_ = 0; + } + + std::string msg; + std::any predicate_data; + if (outer_->predicate) { + if (!outer_->predicate(chvs, dt, msg, predicate_data)) { + if (c.log && !msg.empty() && c.error_info.message_pos < s) { + c.error_info.message_pos = s; + c.error_info.message = msg; + c.error_info.label = outer_->name; + } + parse_len = static_cast(-1); } + } + + if (success(parse_len)) { + if (!c.recovered) { parse_val = reduce(chvs, dt, predicate_data); } + } else { + if (c.log && !msg.empty() && c.error_info.message_pos < s) { + c.error_info.message_pos = s; + c.error_info.message = msg; + c.error_info.label = outer_->name; + } + } + } else { + if (c.log && !outer_->error_message.empty() && + c.error_info.message_pos < s) { + c.error_info.message_pos = s; + c.error_info.message = outer_->error_message; + c.error_info.label = outer_->name; + } } - return len; + return std::make_pair(parse_len, std::move(parse_val)); + }; + + if (outer_->is_left_recursive) { + auto lr_key = std::make_pair(outer_, s); + + // Check LR memo first + auto it = c.lr_memo.find(lr_key); + if (it != c.lr_memo.end()) { + if (success(it->second.len)) { + len = it->second.len; + val = it->second.val; + } else { + len = static_cast(-1); + } + // Record that this rule's lr_memo was accessed. + // Any LR rule currently seeding will know we're in its cycle. + c.lr_refs_hit.insert(outer_); + } else { + // Seed with FAIL + c.lr_memo[lr_key] = {static_cast(-1), {}}; + + // Mark as active seed (protects our lr_memo from inner growers) + c.lr_active_seeds.insert(lr_key); + auto seed_guard = scope_exit([&]() { c.lr_active_seeds.erase(lr_key); }); + + // Track which LR rules are referenced during our parse + // to identify cycle members + auto saved_refs = std::move(c.lr_refs_hit); + c.lr_refs_hit.clear(); + + // Initial parse (self-references will hit the FAIL seed) + auto [initial_len, initial_val] = do_parse(); + + // Rules whose lr_memo was hit during our parse are in our cycle. + // If we detected cycle members, we ourselves are also part of + // the cycle, so add self — this lets parent seeders see us as + // a transitive cycle member. + auto cycle_rules = c.lr_refs_hit; + if (!cycle_rules.empty()) { cycle_rules.insert(outer_); } + + // Restore parent's refs and propagate cycle info upward + c.lr_refs_hit = std::move(saved_refs); + c.lr_refs_hit.insert(cycle_rules.begin(), cycle_rules.end()); + + if (!success(initial_len)) { + // Keep FAIL in lr_memo so we don't re-seed + len = static_cast(-1); + } else { + // Got initial seed, now grow + len = initial_len; + val = std::move(initial_val); + c.lr_memo[lr_key] = {len, val}; + + while (true) { + // Clear this rule's packrat cache + c.clear_packrat_cache(s, outer_->id); + + // Clear lr_memo for cycle-dependent rules at this position, + // but NOT for rules currently in their own seeding phase + // (lr_active_seeds) — those are outer growers we must not + // interfere with. + for (auto memo_it = c.lr_memo.begin(); memo_it != c.lr_memo.end();) { + if (memo_it->first.second == s && memo_it->first.first != outer_ && + cycle_rules.count(memo_it->first.first) && + !c.lr_active_seeds.count(memo_it->first)) { + memo_it = c.lr_memo.erase(memo_it); + } else { + ++memo_it; + } + } + + auto [new_len, new_val] = do_parse(); + + if (!success(new_len) || new_len <= len) { + break; // No improvement, done growing + } + + len = new_len; + val = std::move(new_val); + c.lr_memo[lr_key] = {len, val}; + } + } + + // Write final result to packrat cache (lr_memo entry is kept as + // the primary lookup for LR rules at this position) + if (success(len)) { c.write_packrat_cache(s, outer_->id, len, val); } + } + } else { + if (c.enablePackratParsing) { + // Packrat cache acts as re-entry guard (pre-registered as + // failure before fn is called). + c.packrat(s, outer_->id, len, val, [&](std::any &a_val) { + auto [parse_len, parse_val] = do_parse(); + len = parse_len; + if (success(len)) { a_val = std::move(parse_val); } + }); + } else { + // Without packrat, use lr_memo as re-entry guard to prevent + // stack overflow from undetected left recursion. + auto guard_key = std::make_pair(outer_, s); + if (c.lr_memo.count(guard_key)) { + len = static_cast(-1); + } else { + c.lr_memo[guard_key] = {static_cast(-1), {}}; + auto [parse_len, parse_val] = do_parse(); + len = parse_len; + val = std::move(parse_val); + c.lr_memo.erase(guard_key); + } + } + } + + if (success(len)) { + if (!outer_->ignoreSemanticValue) { + vs.emplace_back(std::move(val)); + vs.tags.emplace_back(str2tag(outer_->name)); + } + } + + return len; } -inline std::any Holder::reduce(SemanticValues &vs, std::any &dt) const { - if (outer_->action && !outer_->disable_action) { - return outer_->action(vs, dt); - } else if (vs.empty()) { - return std::any(); - } else { - return std::move(vs.front()); - } +inline std::any Holder::reduce(SemanticValues &vs, std::any &dt, + const std::any &predicate_data) const { + if (outer_->action && !outer_->disable_action) { + return outer_->action(vs, dt, predicate_data); + } else if (vs.empty()) { + return std::any(); + } else { + return std::move(vs.front()); + } } inline const std::string &Holder::name() const { return outer_->name; } inline const std::string &Holder::trace_name() const { - std::call_once(trace_name_init_, - [this]() { trace_name_ = "[" + outer_->name + "]"; }); - return trace_name_; + std::call_once(trace_name_init_, + [this]() { trace_name_ = "[" + outer_->name + "]"; }); + return trace_name_; } inline size_t Reference::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - auto save_ignore_trace_state = c.ignore_trace_state; - if (rule_ && rule_->ignoreSemanticValue) { - c.ignore_trace_state = !c.verbose_trace; - } - auto se1 = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + auto save_ignore_trace_state = c.ignore_trace_state; + if (rule_ && rule_->ignoreSemanticValue) { + c.ignore_trace_state = !c.verbose_trace; + } + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - if (rule_) { - // Reference rule - if (rule_->is_macro) { - // Macro - FindReference vis(c.top_args(), c.rule_stack.back()->params); + if (rule_) { + // Reference rule + if (rule_->is_macro) { + // Macro + FindReference vis(c.top_args(), c.rule_stack.back()->params); - // Collect arguments - std::vector> args; - for (auto arg : args_) { - arg->accept(vis); - args.emplace_back(std::move(vis.found_ope)); - } + // Collect arguments + std::vector> args; + for (const auto &arg : args_) { + arg->accept(vis); + args.emplace_back(std::move(vis.found_ope)); + } - c.push_args(std::move(args)); - auto se2 = scope_exit([&]() { c.pop_args(); }); - auto ope = get_core_operator(); - return ope->parse(s, n, vs, c, dt); - } else { - // Definition - c.push_args(std::vector>()); - auto se3 = scope_exit([&]() { c.pop_args(); }); - auto ope = get_core_operator(); - return ope->parse(s, n, vs, c, dt); - } + c.push_args(std::move(args)); + auto se = scope_exit([&]() { c.pop_args(); }); + return rule_->holder_->parse(s, n, vs, c, dt); } else { - // Reference parameter in macro - const auto &args = c.top_args(); - return args[iarg_]->parse(s, n, vs, c, dt); + // Definition + c.push_args(std::vector>()); + auto se2 = scope_exit([&]() { c.pop_args(); }); + return rule_->holder_->parse(s, n, vs, c, dt); } + } else { + // Reference parameter in macro + const auto &args = c.top_args(); + return args[iarg_]->parse(s, n, vs, c, dt); + } } inline std::shared_ptr Reference::get_core_operator() const { - return rule_->holder_; + return rule_->holder_; } inline size_t BackReference::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - auto size = static_cast(c.capture_scope_stack_size); - for (auto i = size - 1; i >= 0; i--) { - auto index = static_cast(i); - const auto &cs = c.capture_scope_stack[index]; - if (cs.find(name_) != cs.end()) { - const auto &lit = cs.at(name_); - std::once_flag init_is_word; - auto is_word = false; - return parse_literal(s, n, vs, c, dt, lit, init_is_word, is_word, false); - } + for (auto it = c.capture_entries.rbegin(); it != c.capture_entries.rend(); + ++it) { + if (it->first == name_) { + const auto &lit = it->second; + std::once_flag init_is_word; + auto is_word = false; + static const std::string empty; + return parse_literal(s, n, vs, c, dt, lit, init_is_word, is_word, false, + empty); } + } - c.error_info.message_pos = s; - c.error_info.message = "undefined back reference '$" + name_ + "'..."; - return static_cast(-1); + c.error_info.message_pos = s; + c.error_info.message = "undefined back reference '$" + name_ + "'..."; + return static_cast(-1); } inline Definition & PrecedenceClimbing::get_reference_for_binop(Context &c) const { - if (rule_.is_macro) { - // Reference parameter in macro - const auto &args = c.top_args(); - auto iarg = dynamic_cast(*binop_).iarg_; - auto arg = args[iarg]; - return *dynamic_cast(*arg).rule_; - } + if (rule_.is_macro) { + // Reference parameter in macro + const auto &args = c.top_args(); + auto iarg = dynamic_cast(*binop_).iarg_; + auto arg = args[iarg]; + return *dynamic_cast(*arg).rule_; + } - return *dynamic_cast(*binop_).rule_; + return *dynamic_cast(*binop_).rule_; } inline size_t PrecedenceClimbing::parse_expression(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt, size_t min_prec) const { - auto len = atom_->parse(s, n, vs, c, dt); - if (fail(len)) { return len; } + auto len = atom_->parse(s, n, vs, c, dt); + if (fail(len)) { return len; } - std::string tok; - auto &rule = get_reference_for_binop(c); - auto action = std::move(rule.action); + std::string tok; + auto &rule = get_reference_for_binop(c); + auto action = std::move(rule.action); - rule.action = [&](SemanticValues &vs2, std::any &dt2) { - tok = vs2.token(); - if (action) { - return action(vs2, dt2); - } else if (!vs2.empty()) { - return vs2[0]; - } - return std::any(); - }; - auto action_se = scope_exit([&]() { rule.action = std::move(action); }); + rule.action = [&](SemanticValues &vs2, std::any &dt2, + const std::any &predicate_data2) { + tok = vs2.token(); + if (action) { + return action(vs2, dt2, predicate_data2); + } else if (!vs2.empty()) { + return vs2[0]; + } + return std::any(); + }; + auto action_se = scope_exit([&]() { rule.action = std::move(action); }); - auto i = len; - while (i < n) { - std::vector save_values(vs.begin(), vs.end()); - auto save_tokens = vs.tokens; + auto i = len; + while (i < n) { + std::vector save_values(vs.begin(), vs.end()); + auto save_tokens = vs.tokens; - auto chvs = c.push_semantic_values_scope(); - auto chlen = binop_->parse(s + i, n - i, chvs, c, dt); - c.pop_semantic_values_scope(); + auto chvs = c.push_semantic_values_scope(); + auto chlen = binop_->parse(s + i, n - i, chvs, c, dt); + c.pop_semantic_values_scope(); - if (fail(chlen)) { break; } + if (fail(chlen)) { break; } - auto it = info_.find(tok); - if (it == info_.end()) { break; } + auto it = info_.find(tok); + if (it == info_.end()) { break; } - auto level = std::get<0>(it->second); - auto assoc = std::get<1>(it->second); + auto level = std::get<0>(it->second); + auto assoc = std::get<1>(it->second); - if (level < min_prec) { break; } + if (level < min_prec) { break; } - vs.emplace_back(std::move(chvs[0])); - i += chlen; + vs.emplace_back(std::move(chvs[0])); + i += chlen; - auto next_min_prec = level; - if (assoc == 'L') { next_min_prec = level + 1; } + auto next_min_prec = level; + if (assoc == 'L') { next_min_prec = level + 1; } - chvs = c.push_semantic_values_scope(); - chlen = parse_expression(s + i, n - i, chvs, c, dt, next_min_prec); - c.pop_semantic_values_scope(); + chvs = c.push_semantic_values_scope(); + chlen = parse_expression(s + i, n - i, chvs, c, dt, next_min_prec); + c.pop_semantic_values_scope(); - if (fail(chlen)) { - vs.assign(save_values.begin(), save_values.end()); - vs.tokens = save_tokens; - i = chlen; - break; - } - - vs.emplace_back(std::move(chvs[0])); - i += chlen; - - std::any val; - if (rule_.action) { - vs.sv_ = std::string_view(s, i); - val = rule_.action(vs, dt); - } else if (!vs.empty()) { - val = vs[0]; - } - vs.clear(); - vs.emplace_back(std::move(val)); + if (fail(chlen)) { + vs.assign(save_values.begin(), save_values.end()); + vs.tokens = save_tokens; + i = chlen; + break; } - return i; + vs.emplace_back(std::move(chvs[0])); + i += chlen; + + std::any val; + if (rule_.action) { + vs.sv_ = std::string_view(s, i); + static const std::any empty_predicate_data; + val = rule_.action(vs, dt, empty_predicate_data); + } else if (!vs.empty()) { + val = vs[0]; + } + vs.clear(); + vs.emplace_back(std::move(val)); + } + + return i; } inline size_t Recovery::parse_core(const char *s, size_t n, SemanticValues & /*vs*/, Context &c, std::any & /*dt*/) const { - const auto &rule = dynamic_cast(*ope_); + const auto &rule = dynamic_cast(*ope_); + + // Custom error message + if (c.log) { + auto label = dynamic_cast(rule.args_[0].get()); + if (label && !label->rule_->error_message.empty()) { + c.error_info.message_pos = s; + c.error_info.message = label->rule_->error_message; + c.error_info.label = label->rule_->name; + } + } + + // Recovery + auto len = static_cast(-1); + { + auto save_log = c.log; + c.log = nullptr; + auto se = scope_exit([&]() { c.log = save_log; }); + + SemanticValues dummy_vs; + std::any dummy_dt; + + len = rule.parse(s, n, dummy_vs, c, dummy_dt); + } + + if (success(len)) { + c.recovered = true; - // Custom error message if (c.log) { - auto label = dynamic_cast(rule.args_[0].get()); - if (label && !label->rule_->error_message.empty()) { - c.error_info.message_pos = s; - c.error_info.message = label->rule_->error_message; - c.error_info.label = label->rule_->name; - } + c.error_info.output_log(c.log, c.s, c.l); + c.error_info.clear(); } + } - // Recovery - auto len = static_cast(-1); - { - auto save_log = c.log; - c.log = nullptr; - auto se = scope_exit([&]() { c.log = save_log; }); + // Cut + if (!c.cut_stack.empty()) { + c.cut_stack.back() = true; - SemanticValues dummy_vs; - std::any dummy_dt; - - len = rule.parse(s, n, dummy_vs, c, dummy_dt); + if (c.cut_stack.size() == 1) { + // TODO: Remove unneeded entries in packrat memoise table } + } - if (success(len)) { - c.recovered = true; - - if (c.log) { - c.error_info.output_log(c.log, c.s, c.l); - c.error_info.clear(); - } - } - - // Cut - if (!c.cut_stack.empty()) { - c.cut_stack.back() = true; - - if (c.cut_stack.size() == 1) { - //! \todo Remove unneeded entries in packrat memoise table - } - } - - return len; + return len; } inline void Sequence::accept(Visitor &v) { v.visit(*this); } @@ -3100,194 +3601,452 @@ inline void Recovery::accept(Visitor &v) { v.visit(*this); } inline void Cut::accept(Visitor &v) { v.visit(*this); } inline void AssignIDToDefinition::visit(Holder &ope) { - auto p = static_cast(ope.outer_); - if (ids.count(p)) { return; } - auto id = ids.size(); - ids[p] = id; - ope.outer_->id = id; - ope.ope_->accept(*this); + auto p = static_cast(ope.outer_); + if (ids.count(p)) { return; } + auto id = ids.size(); + ids[p] = id; + ope.outer_->id = id; + ope.ope_->accept(*this); } inline void AssignIDToDefinition::visit(Reference &ope) { - if (ope.rule_) { - for (auto arg : ope.args_) { - arg->accept(*this); - } - ope.rule_->accept(*this); + if (ope.rule_) { + for (const auto &arg : ope.args_) { + arg->accept(*this); } + ope.rule_->accept(*this); + } } inline void AssignIDToDefinition::visit(PrecedenceClimbing &ope) { - ope.atom_->accept(*this); - ope.binop_->accept(*this); + ope.atom_->accept(*this); + ope.binop_->accept(*this); } inline void TokenChecker::visit(Reference &ope) { - if (ope.is_macro_) { - for (auto arg : ope.args_) { - arg->accept(*this); - } - } else { - has_rule_ = true; + if (ope.is_macro_) { + for (const auto &arg : ope.args_) { + arg->accept(*this); } + } else { + has_rule_ = true; + } } inline void FindLiteralToken::visit(Reference &ope) { - if (ope.is_macro_) { - ope.rule_->accept(*this); - for (auto arg : ope.args_) { - arg->accept(*this); - } + if (ope.is_macro_) { + ope.rule_->accept(*this); + for (const auto &arg : ope.args_) { + arg->accept(*this); } + } +} + +inline void ComputeCanBeEmpty::visit(Reference &ope) { + result = ope.rule_ && ope.rule_->can_be_empty; } inline void DetectLeftRecursion::visit(Reference &ope) { - if (ope.name_ == name_) { - error_s = ope.s_; - } else if (!refs_.count(ope.name_)) { - refs_.insert(ope.name_); - if (ope.rule_) { - ope.rule_->accept(*this); - if (done_ == false) { return; } - } + if (ope.name_ == name_) { + error_s = ope.s_; + } else if (!ope.rule_ && !macro_args_stack_.empty()) { + // Macro parameter reference: resolve through nested macro arg + // stacks (e.g. B(X) <- C(X) where X is itself a param ref). + auto resolved = resolve_macro_arg(ope.iarg_); + if (resolved) { + resolved->accept(*this); + if (done_ == false) { return; } } - done_ = true; + } else if (!refs_.count(ope.name_)) { + refs_.insert(ope.name_); + if (ope.rule_) { + if (ope.is_macro_) { macro_args_stack_.push_back(&ope.args_); } + ope.rule_->accept(*this); + if (ope.is_macro_) { macro_args_stack_.pop_back(); } + if (done_ == false) { return; } + } + } + // If the referenced rule can match empty, don't mark as done — + // the sequence may continue past this element to find LR. + if (!ope.rule_ && !macro_args_stack_.empty()) { + auto resolved = resolve_macro_arg(ope.iarg_); + if (resolved) { + ComputeCanBeEmpty cbe; + resolved->accept(cbe); + done_ = !cbe.result; + } else { + done_ = true; + } + } else { + done_ = !(ope.rule_ && ope.rule_->can_be_empty); + } +} + +inline std::shared_ptr +DetectLeftRecursion::resolve_macro_arg(size_t iarg) const { + for (int i = static_cast(macro_args_stack_.size()) - 1; i >= 0; i--) { + auto &args = *macro_args_stack_[i]; + if (iarg >= args.size()) { return nullptr; } + auto ref = dynamic_cast(args[iarg].get()); + if (ref && !ref->rule_) { + // Another param ref — resolve using parent level's args + iarg = ref->iarg_; + continue; + } + return args[iarg]; + } + return nullptr; } inline void HasEmptyElement::visit(Sequence &ope) { - auto save_is_empty = false; - const char *save_error_s = nullptr; - std::string save_error_name; + auto save_is_empty = false; + const char *save_error_s = nullptr; + std::string save_error_name; - auto it = ope.opes_.begin(); - while (it != ope.opes_.end()) { - (*it)->accept(*this); - if (!is_empty) { - ++it; - while (it != ope.opes_.end()) { - DetectInfiniteLoop vis(refs_, has_error_cache_); - (*it)->accept(vis); - if (vis.has_error) { - is_empty = true; - error_s = vis.error_s; - error_name = vis.error_name; - } - ++it; - } - return; + auto it = ope.opes_.begin(); + while (it != ope.opes_.end()) { + (*it)->accept(*this); + if (!is_empty) { + ++it; + while (it != ope.opes_.end()) { + DetectInfiniteLoop vis(refs_, has_error_cache_); + (*it)->accept(vis); + if (vis.has_error) { + is_empty = true; + error_s = vis.error_s; + error_name = vis.error_name; } - - save_is_empty = is_empty; - save_error_s = error_s; - save_error_name = error_name; - - is_empty = false; - error_name.clear(); ++it; + } + return; } - is_empty = save_is_empty; - error_s = save_error_s; - error_name = save_error_name; + save_is_empty = is_empty; + save_error_s = error_s; + save_error_name = error_name; + + is_empty = false; + error_name.clear(); + ++it; + } + + is_empty = save_is_empty; + error_s = save_error_s; + error_name = save_error_name; } inline void HasEmptyElement::visit(Reference &ope) { - auto it = std::find_if(refs_.begin(), refs_.end(), - [&](const std::pair &ref) { - return ope.name_ == ref.second; - }); - if (it != refs_.end()) { return; } + auto it = std::find_if(refs_.begin(), refs_.end(), + [&](const std::pair &ref) { + return ope.name_ == ref.second; + }); + if (it != refs_.end()) { return; } - if (ope.rule_) { - refs_.emplace_back(ope.s_, ope.name_); - ope.rule_->accept(*this); - refs_.pop_back(); - } + if (ope.rule_) { + refs_.emplace_back(ope.s_, ope.name_); + ope.rule_->accept(*this); + refs_.pop_back(); + } } inline void DetectInfiniteLoop::visit(Reference &ope) { - auto it1 = std::find_if(refs_.begin(), refs_.end(), - [&](const std::pair &ref) { - return ope.name_ == ref.second; - }); - if (it1 != refs_.end()) { return; } + auto it = std::find_if(refs_.begin(), refs_.end(), + [&](const std::pair &ref) { + return ope.name_ == ref.second; + }); + if (it != refs_.end()) { return; } - if (ope.rule_) { - auto it = has_error_cache_.find(ope.name_); - if (it != has_error_cache_.end()) { - has_error = it->second; - } else { - refs_.emplace_back(ope.s_, ope.name_); - ope.rule_->accept(*this); - refs_.pop_back(); - has_error_cache_[ope.name_] = has_error; - } + if (ope.rule_) { + auto it = has_error_cache_.find(ope.name_); + if (it != has_error_cache_.end()) { + has_error = it->second; + } else { + refs_.emplace_back(ope.s_, ope.name_); + ope.rule_->accept(*this); + refs_.pop_back(); + has_error_cache_[ope.name_] = has_error; } + } - if (ope.is_macro_) { - for (auto arg : ope.args_) { - arg->accept(*this); - } + if (ope.is_macro_) { + for (const auto &arg : ope.args_) { + arg->accept(*this); } + } } inline void ReferenceChecker::visit(Reference &ope) { - auto it = std::find(params_.begin(), params_.end(), ope.name_); - if (it != params_.end()) { return; } + auto it = std::find(params_.begin(), params_.end(), ope.name_); + if (it != params_.end()) { return; } - if (!grammar_.count(ope.name_)) { + if (!grammar_.count(ope.name_)) { + error_s[ope.name_] = ope.s_; + error_message[ope.name_] = "'" + ope.name_ + "' is not defined."; + } else { + if (!referenced.count(ope.name_)) { referenced.insert(ope.name_); } + const auto &rule = grammar_.at(ope.name_); + if (rule.is_macro) { + if (!ope.is_macro_ || ope.args_.size() != rule.params.size()) { error_s[ope.name_] = ope.s_; - error_message[ope.name_] = "'" + ope.name_ + "' is not defined."; - } else { - if (!referenced.count(ope.name_)) { referenced.insert(ope.name_); } - const auto &rule = grammar_.at(ope.name_); - if (rule.is_macro) { - if (!ope.is_macro_ || ope.args_.size() != rule.params.size()) { - error_s[ope.name_] = ope.s_; - error_message[ope.name_] = "incorrect number of arguments."; - } - } else if (ope.is_macro_) { - error_s[ope.name_] = ope.s_; - error_message[ope.name_] = "'" + ope.name_ + "' is not macro."; - } - for (auto arg : ope.args_) { - arg->accept(*this); - } + error_message[ope.name_] = "incorrect number of arguments."; + } + } else if (ope.is_macro_) { + error_s[ope.name_] = ope.s_; + error_message[ope.name_] = "'" + ope.name_ + "' is not macro."; } + for (const auto &arg : ope.args_) { + arg->accept(*this); + } + } +} + +inline void ComputeFirstSet::visit(Reference &ope) { + if (!ope.rule_) { + // Macro parameter reference — can't predict what it will match + result_.any_char = true; + return; + } + if (refs_.count(ope.name_)) { return; } + refs_.insert(ope.name_); + ope.rule_->accept(*this); + if (!result_.first_rule && ope.rule_->is_token()) { + result_.first_rule = ope.rule_; + } + refs_.erase(ope.name_); +} + +inline void SetupFirstSets::visit(Reference &ope) { + if (!ope.rule_ || refs_.count(ope.name_)) { return; } + refs_.insert(ope.name_); + ope.rule_->accept(*this); + refs_.erase(ope.name_); +} + +inline void SetupFirstSets::visit(Sequence &ope) { + ope.kw_guard_.reset(); + setup_keyword_guarded_identifier(ope); + for (const auto &op : ope.opes_) { + op->accept(*this); + } +} + +inline void SetupFirstSets::setup_keyword_guarded_identifier(Sequence &seq) { + // Detect pattern: NotPredicate(Reference→PrioritizedChoice) + // TokenBoundary(Sequence[CharacterClass, + // Repetition(CharacterClass)]) + // This is the pattern used by: PlainIdentifier <- !ReservedKeyword + // <[a-z_]i[a-z0-9_]i*> + if (seq.opes_.size() != 2) { return; } + + // Child 0 must be NotPredicate + auto *not_pred = dynamic_cast(seq.opes_[0].get()); + if (!not_pred) { return; } + + // NotPredicate's child must be Reference to a rule + auto *ref = dynamic_cast(not_pred->ope_.get()); + if (!ref || !ref->rule_) { return; } + + // The referenced rule's inner operator (Holder) must contain + // PrioritizedChoice + auto *holder = dynamic_cast(ref->get_core_operator().get()); + if (!holder) { return; } + auto *choice = dynamic_cast(holder->ope_.get()); + if (!choice) { return; } + + // Extract keywords from PrioritizedChoice alternatives + std::vector exact_keywords; + std::vector prefix_keywords; + + for (const auto &alt : choice->opes_) { + auto *lit = dynamic_cast(alt.get()); + if (lit) { + if (!lit->ignore_case_) { return; } + exact_keywords.push_back(to_lower(lit->lit_)); + continue; + } + // Check for compound keyword (Sequence of LiteralStrings) + auto *sub_seq = dynamic_cast(alt.get()); + if (sub_seq && !sub_seq->opes_.empty()) { + auto *first_lit = dynamic_cast(sub_seq->opes_[0].get()); + if (first_lit) { + auto all_ignore_case_lits = + std::all_of(sub_seq->opes_.begin(), sub_seq->opes_.end(), + [](const auto &child) { + auto *l = dynamic_cast(child.get()); + return l && l->ignore_case_; + }); + if (all_ignore_case_lits) { + prefix_keywords.push_back(to_lower(first_lit->lit_)); + continue; + } + } + } + // Unrecognized alternative — bail out + return; + } + + if (exact_keywords.empty()) { return; } + + // Child 1 must be TokenBoundary + auto *tb = dynamic_cast(seq.opes_[1].get()); + if (!tb) { return; } + + // TokenBoundary content: Sequence[CharacterClass, Repetition(CharacterClass)] + // or just CharacterClass (single char identifier) + CharacterClass *first_cc = nullptr; + CharacterClass *rest_cc = nullptr; + + auto *inner_seq = dynamic_cast(tb->ope_.get()); + if (inner_seq && inner_seq->opes_.size() == 2) { + first_cc = dynamic_cast(inner_seq->opes_[0].get()); + auto *rep = dynamic_cast(inner_seq->opes_[1].get()); + if (rep) { rest_cc = dynamic_cast(rep->ope_.get()); } + } + + if (!first_cc || !rest_cc) { return; } + if (!first_cc->is_ascii_only() || !rest_cc->is_ascii_only()) { return; } + + // All conditions met — set up the fast path + auto kw = std::make_unique(); + kw->identifier_first = first_cc->ascii_bitset(); + kw->identifier_rest = rest_cc->ascii_bitset(); + + // Compute keyword length range for early-out in hot path + size_t min_len = SIZE_MAX, max_len = 0; + for (const auto &k : exact_keywords) { + min_len = std::min(min_len, k.size()); + max_len = std::max(max_len, k.size()); + } + for (const auto &k : prefix_keywords) { + min_len = std::min(min_len, k.size()); + max_len = std::max(max_len, k.size()); + } + kw->min_keyword_len = min_len; + kw->max_keyword_len = max_len; + + kw->exact_keywords = std::move(exact_keywords); + kw->prefix_keywords = std::move(prefix_keywords); + seq.kw_guard_ = std::move(kw); +} + +// Compute which rules benefit from packrat memoization. +// A rule benefits if it's reachable from 2+ alternatives of the same +// PrioritizedChoice (backtracking will re-visit it at the same position). +inline void Definition::initialize_packrat_filter() const { + std::call_once(packrat_filter_init_, [&]() { + auto def_count = definition_ids_.size(); + if (def_count == 0) { return; } + + // Collect rule IDs reachable from an Ope subtree (bitvector indexed by + // def_id) + struct CollectReachableRules : public TraversalVisitor { + using TraversalVisitor::visit; + std::vector reachable; // indexed by def_id + + CollectReachableRules(size_t n) : reachable(n, false) {} + + void visit(Holder &ope) override { + auto id = ope.outer_->id; + if (id < reachable.size()) { reachable[id] = true; } + ope.ope_->accept(*this); + } + void visit(Reference &ope) override { + if (ope.rule_ && ope.rule_->id < reachable.size() && + !reachable[ope.rule_->id]) { + reachable[ope.rule_->id] = true; + ope.rule_->accept(*this); + } + } + }; + + // Find rules that benefit: reachable from 2+ alternatives of same choice + std::vector benefits(def_count, false); + + struct FindBacktrackRules : public TraversalVisitor { + using TraversalVisitor::visit; + std::vector &benefits; + size_t def_count; + std::vector visited_rules; // indexed by def_id + + FindBacktrackRules(std::vector &b, size_t n) + : benefits(b), def_count(n), visited_rules(n, false) {} + + void visit(PrioritizedChoice &ope) override { + // For each alternative, collect reachable rules as bitvectors + std::vector> alt_reachable; + for (auto &op : ope.opes_) { + CollectReachableRules crr(def_count); + op->accept(crr); + alt_reachable.push_back(std::move(crr.reachable)); + } + + // Mark rules reachable from 2+ alternatives + for (size_t id = 0; id < def_count; id++) { + size_t count = 0; + for (auto &alt : alt_reachable) { + if (alt[id]) { count++; } + } + if (count >= 2) { benefits[id] = true; } + } + + // Recurse into alternatives + for (auto &op : ope.opes_) { + op->accept(*this); + } + } + void visit(Holder &ope) override { + auto id = ope.outer_->id; + if (id < visited_rules.size() && !visited_rules[id]) { + visited_rules[id] = true; + ope.ope_->accept(*this); + } + } + void visit(Reference &ope) override { + if (ope.rule_) { ope.rule_->accept(*this); } + } + }; + + FindBacktrackRules finder(benefits, def_count); + holder_->accept(finder); + if (whitespaceOpe) { whitespaceOpe->accept(finder); } + if (wordOpe) { wordOpe->accept(finder); } + + packrat_filter_ = std::move(benefits); + }); } inline void LinkReferences::visit(Reference &ope) { - // Check if the reference is a macro parameter - auto found_param = false; - for (size_t i = 0; i < params_.size(); i++) { - const auto ¶m = params_[i]; - if (param == ope.name_) { - ope.iarg_ = i; - found_param = true; - break; - } + // Check if the reference is a macro parameter + auto found_param = false; + for (size_t i = 0; i < params_.size(); i++) { + const auto ¶m = params_[i]; + if (param == ope.name_) { + ope.iarg_ = i; + found_param = true; + break; } + } - // Check if the reference is a definition rule - if (!found_param && grammar_.count(ope.name_)) { - auto &rule = grammar_.at(ope.name_); - ope.rule_ = &rule; - } + // Check if the reference is a definition rule + if (!found_param && grammar_.count(ope.name_)) { + auto &rule = grammar_.at(ope.name_); + ope.rule_ = &rule; + } - for (auto arg : ope.args_) { - arg->accept(*this); - } + for (const auto &arg : ope.args_) { + arg->accept(*this); + } } inline void FindReference::visit(Reference &ope) { - for (size_t i = 0; i < args_.size(); i++) { - const auto &name = params_[i]; - if (name == ope.name_) { - found_ope = args_[i]; - return; - } + for (size_t i = 0; i < args_.size(); i++) { + const auto &name = params_[i]; + if (name == ope.name_) { + found_ope = args_[i]; + return; } - found_ope = ope.shared_from_this(); + } + found_ope = ope.shared_from_this(); } /*----------------------------------------------------------------------------- @@ -3298,955 +4057,1011 @@ using Rules = std::unordered_map>; class ParserGenerator { public: - struct ParserContext { - std::shared_ptr grammar; - std::string start; - bool enablePackratParsing = false; - }; + struct ParserContext { + std::shared_ptr grammar; + std::string start; + bool enablePackratParsing = false; + }; - static ParserContext parse(const char *s, size_t n, const Rules &rules, - Log log, std::string_view start) { - return get_instance().perform_core(s, n, rules, log, std::string(start)); - } + static ParserContext parse(const char *s, size_t n, const Rules &rules, + Log log, std::string_view start, + bool enable_left_recursion = true) { + return get_instance().perform_core(s, n, rules, log, std::string(start), + enable_left_recursion); + } - // For debugging purpose - static bool parse_test(const char *d, const char *s) { - Data data; - std::any dt = &data; + // For debugging purpose + static bool parse_test(const char *d, const char *s) { + Data data; + std::any dt = &data; - auto n = strlen(s); - auto r = get_instance().g[d].parse(s, n, dt); - return r.ret && r.len == n; - } + auto n = strlen(s); + auto r = get_instance().g[d].parse(s, n, dt); + return r.ret && r.len == n; + } #if defined(__cpp_lib_char8_t) - static bool parse_test(const char *d, const char8_t *s) { - return parse_test(d, reinterpret_cast(s)); - } + static bool parse_test(const char *d, const char8_t *s) { + return parse_test(d, reinterpret_cast(s)); + } #endif private: - static ParserGenerator &get_instance() { - static ParserGenerator instance; - return instance; - } + static ParserGenerator &get_instance() { + static ParserGenerator instance; + return instance; + } - ParserGenerator() { - make_grammar(); - setup_actions(); - } + ParserGenerator() { + make_grammar(); + setup_actions(); + } - struct Instruction { - std::string type; - std::any data; - std::string_view sv; + struct Instruction { + std::string type; + std::any data; + std::string_view sv; + }; + + struct Data { + std::shared_ptr grammar; + std::string start; + const char *start_pos = nullptr; + + std::vector> duplicates_of_definition; + + std::vector> duplicates_of_instruction; + std::map> instructions; + + std::vector> undefined_back_references; + std::vector> captures_stack{{}}; + + std::set captures_in_current_definition; + bool enablePackratParsing = true; + + Data() : grammar(std::make_shared()) {} + }; + + class SyntaxErrorException : public std::runtime_error { + public: + SyntaxErrorException(const char *what_arg, std::pair r) + : std::runtime_error(what_arg), r_(r) {} + + std::pair line_info() const { return r_; } + + private: + std::pair r_; + }; + + void make_grammar() { + // Setup PEG syntax parser + g["Grammar"] <= seq(g["Spacing"], oom(g["Definition"]), g["EndOfFile"]); + g["Definition"] <= + cho(seq(g["Ignore"], g["IdentCont"], g["Parameters"], g["LEFTARROW"], + g["Expression"], opt(g["Instruction"])), + seq(g["Ignore"], g["Identifier"], g["LEFTARROW"], g["Expression"], + opt(g["Instruction"]))); + g["Expression"] <= seq(g["Sequence"], zom(seq(g["SLASH"], g["Sequence"]))); + g["Sequence"] <= zom(cho(g["CUT"], g["Prefix"])); + g["Prefix"] <= seq(opt(cho(g["AND"], g["NOT"])), g["SuffixWithLabel"]); + g["SuffixWithLabel"] <= + seq(g["Suffix"], opt(seq(g["LABEL"], g["Identifier"]))); + g["Suffix"] <= seq(g["Primary"], opt(g["Loop"])); + g["Loop"] <= cho(g["QUESTION"], g["STAR"], g["PLUS"], g["Repetition"]); + g["Primary"] <= cho(seq(g["Ignore"], g["IdentCont"], g["Arguments"], + npd(g["LEFTARROW"])), + seq(g["Ignore"], g["Identifier"], + npd(seq(opt(g["Parameters"]), g["LEFTARROW"]))), + seq(g["OPEN"], g["Expression"], g["CLOSE"]), + seq(g["BeginTok"], g["Expression"], g["EndTok"]), + g["CapScope"], + seq(g["BeginCap"], g["Expression"], g["EndCap"]), + g["BackRef"], g["DictionaryI"], g["LiteralI"], + g["Dictionary"], g["Literal"], g["NegatedClassI"], + g["NegatedClass"], g["ClassI"], g["Class"], g["DOT"]); + + g["Identifier"] <= seq(g["IdentCont"], g["Spacing"]); + g["IdentCont"] <= tok(seq(g["IdentStart"], zom(g["IdentRest"]))); + + const static std::vector> range = { + {0x0080, 0xFFFF}}; + g["IdentStart"] <= seq(npd(lit(u8(u8"↑"))), npd(lit(u8(u8"⇑"))), + cho(cls("a-zA-Z_%"), cls(range))); + + g["IdentRest"] <= cho(g["IdentStart"], cls("0-9")); + + g["Dictionary"] <= seq(g["LiteralD"], oom(seq(g["PIPE"], g["LiteralD"]))); + + g["DictionaryI"] <= + seq(g["LiteralID"], oom(seq(g["PIPE"], g["LiteralID"]))); + + auto lit_ope = cho(seq(cls("'"), tok(zom(seq(npd(cls("'")), g["Char"]))), + cls("'"), g["Spacing"]), + seq(cls("\""), tok(zom(seq(npd(cls("\"")), g["Char"]))), + cls("\""), g["Spacing"])); + g["Literal"] <= lit_ope; + g["LiteralD"] <= lit_ope; + + auto lit_case_ignore_ope = + cho(seq(cls("'"), tok(zom(seq(npd(cls("'")), g["Char"]))), lit("'i"), + g["Spacing"]), + seq(cls("\""), tok(zom(seq(npd(cls("\"")), g["Char"]))), lit("\"i"), + g["Spacing"])); + g["LiteralI"] <= lit_case_ignore_ope; + g["LiteralID"] <= lit_case_ignore_ope; + + // NOTE: The original Brian Ford's paper uses 'zom' instead of 'oom'. + g["Class"] <= seq(chr('['), npd(chr('^')), + tok(oom(seq(npd(chr(']')), g["Range"]))), chr(']'), + g["Spacing"]); + g["ClassI"] <= seq(chr('['), npd(chr('^')), + tok(oom(seq(npd(chr(']')), g["Range"]))), lit("]i"), + g["Spacing"]); + + g["NegatedClass"] <= seq(lit("[^"), + tok(oom(seq(npd(chr(']')), g["Range"]))), chr(']'), + g["Spacing"]); + g["NegatedClassI"] <= seq(lit("[^"), + tok(oom(seq(npd(chr(']')), g["Range"]))), + lit("]i"), g["Spacing"]); + + // NOTE: This is different from The original Brian Ford's paper, and this + // modification allows us to specify `[+-]` as a valid char class. + g["Range"] <= + cho(seq(g["Char"], chr('-'), npd(chr(']')), g["Char"]), g["Char"]); + + g["Char"] <= + cho(seq(chr('\\'), cls("fnrtv'\"[]\\^-")), + seq(chr('\\'), cls("0-3"), cls("0-7"), cls("0-7")), + seq(chr('\\'), cls("0-7"), opt(cls("0-7"))), + seq(lit("\\x"), cls("0-9a-fA-F"), opt(cls("0-9a-fA-F"))), + seq(lit("\\u"), + cho(seq(cho(seq(chr('0'), cls("0-9a-fA-F")), lit("10")), + rep(cls("0-9a-fA-F"), 4, 4)), + rep(cls("0-9a-fA-F"), 4, 5))), + seq(npd(chr('\\')), dot())); + + g["Repetition"] <= + seq(g["BeginBracket"], g["RepetitionRange"], g["EndBracket"]); + g["RepetitionRange"] <= cho(seq(g["Number"], g["COMMA"], g["Number"]), + seq(g["Number"], g["COMMA"]), g["Number"], + seq(g["COMMA"], g["Number"])); + g["Number"] <= seq(oom(cls("0-9")), g["Spacing"]); + + g["CapScope"] <= seq(g["BeginCapScope"], g["Expression"], g["EndCapScope"]); + + g["LEFTARROW"] <= seq(cho(lit("<-"), lit(u8(u8"←"))), g["Spacing"]); + ~g["SLASH"] <= seq(chr('/'), g["Spacing"]); + ~g["PIPE"] <= seq(chr('|'), g["Spacing"]); + g["AND"] <= seq(chr('&'), g["Spacing"]); + g["NOT"] <= seq(chr('!'), g["Spacing"]); + g["QUESTION"] <= seq(chr('?'), g["Spacing"]); + g["STAR"] <= seq(chr('*'), g["Spacing"]); + g["PLUS"] <= seq(chr('+'), g["Spacing"]); + ~g["OPEN"] <= seq(chr('('), g["Spacing"]); + ~g["CLOSE"] <= seq(chr(')'), g["Spacing"]); + g["DOT"] <= seq(chr('.'), g["Spacing"]); + + g["CUT"] <= seq(lit(u8(u8"↑")), g["Spacing"]); + ~g["LABEL"] <= seq(cho(chr('^'), lit(u8(u8"⇑"))), g["Spacing"]); + + ~g["Spacing"] <= zom(cho(g["Space"], g["Comment"])); + g["Comment"] <= seq(chr('#'), zom(seq(npd(g["EndOfLine"]), dot())), + opt(g["EndOfLine"])); + g["Space"] <= cho(chr(' '), chr('\t'), g["EndOfLine"]); + g["EndOfLine"] <= cho(lit("\r\n"), chr('\n'), chr('\r')); + g["EndOfFile"] <= npd(dot()); + + ~g["BeginTok"] <= seq(chr('<'), g["Spacing"]); + ~g["EndTok"] <= seq(chr('>'), g["Spacing"]); + + ~g["BeginCapScope"] <= seq(chr('$'), chr('('), g["Spacing"]); + ~g["EndCapScope"] <= seq(chr(')'), g["Spacing"]); + + g["BeginCap"] <= seq(chr('$'), tok(g["IdentCont"]), chr('<'), g["Spacing"]); + ~g["EndCap"] <= seq(chr('>'), g["Spacing"]); + + g["BackRef"] <= seq(chr('$'), tok(g["IdentCont"]), g["Spacing"]); + + g["IGNORE"] <= chr('~'); + + g["Ignore"] <= opt(g["IGNORE"]); + g["Parameters"] <= seq(g["OPEN"], g["Identifier"], + zom(seq(g["COMMA"], g["Identifier"])), g["CLOSE"]); + g["Arguments"] <= seq(g["OPEN"], g["Expression"], + zom(seq(g["COMMA"], g["Expression"])), g["CLOSE"]); + ~g["COMMA"] <= seq(chr(','), g["Spacing"]); + + // Instruction grammars + g["Instruction"] <= + seq(g["BeginBracket"], + opt(seq(g["InstructionItem"], zom(seq(g["InstructionItemSeparator"], + g["InstructionItem"])))), + g["EndBracket"]); + g["InstructionItem"] <= + cho(g["PrecedenceClimbing"], g["ErrorMessage"], g["NoAstOpt"]); + ~g["InstructionItemSeparator"] <= seq(chr(';'), g["Spacing"]); + + ~g["SpacesZom"] <= zom(g["Space"]); + ~g["SpacesOom"] <= oom(g["Space"]); + ~g["BeginBracket"] <= seq(chr('{'), g["Spacing"]); + ~g["EndBracket"] <= seq(chr('}'), g["Spacing"]); + + // PrecedenceClimbing instruction + g["PrecedenceClimbing"] <= + seq(lit("precedence"), g["SpacesOom"], g["PrecedenceInfo"], + zom(seq(g["SpacesOom"], g["PrecedenceInfo"])), g["SpacesZom"]); + g["PrecedenceInfo"] <= + seq(g["PrecedenceAssoc"], + oom(seq(ign(g["SpacesOom"]), g["PrecedenceOpe"]))); + g["PrecedenceOpe"] <= + cho(seq(cls("'"), + tok(zom(seq(npd(cho(g["Space"], cls("'"))), g["Char"]))), + cls("'")), + seq(cls("\""), + tok(zom(seq(npd(cho(g["Space"], cls("\""))), g["Char"]))), + cls("\"")), + tok(oom(seq(npd(cho(g["PrecedenceAssoc"], g["Space"], chr('}'))), + dot())))); + g["PrecedenceAssoc"] <= cls("LR"); + + // Error message instruction + g["ErrorMessage"] <= seq(lit("error_message"), g["SpacesOom"], + g["LiteralD"], g["SpacesZom"]); + + // No Ast node optimization instruction + g["NoAstOpt"] <= seq(lit("no_ast_opt"), g["SpacesZom"]); + + // Set definition names + for (auto &x : g) { + x.second.name = x.first; + } + } + + void setup_actions() { + g["Definition"] = [&](const SemanticValues &vs, std::any &dt) { + auto &data = *std::any_cast(dt); + + auto is_macro = vs.choice() == 0; + auto ignore = std::any_cast(vs[0]); + auto name = std::any_cast(vs[1]); + + std::vector params; + std::shared_ptr ope; + auto has_instructions = false; + + if (is_macro) { + params = std::any_cast>(vs[2]); + ope = std::any_cast>(vs[4]); + if (vs.size() == 6) { has_instructions = true; } + } else { + ope = std::any_cast>(vs[3]); + if (vs.size() == 5) { has_instructions = true; } + } + + if (has_instructions) { + auto index = is_macro ? 5 : 4; + std::unordered_set types; + for (const auto &instruction : + std::any_cast>(vs[index])) { + const auto &type = instruction.type; + if (types.find(type) == types.end()) { + data.instructions[name].push_back(instruction); + types.insert(instruction.type); + if (type == "declare_symbol" || type == "check_symbol") { + if (!TokenChecker::is_token(*ope)) { ope = tok(ope); } + } + } else { + data.duplicates_of_instruction.emplace_back(type, + instruction.sv.data()); + } + } + } + + auto &grammar = *data.grammar; + if (!grammar.count(name)) { + auto &rule = grammar[name]; + rule <= ope; + rule.name = name; + rule.s_ = vs.sv().data(); + rule.line_ = line_info(vs.ss, rule.s_); + rule.ignoreSemanticValue = ignore; + rule.is_macro = is_macro; + rule.params = params; + + if (data.start.empty()) { + data.start = rule.name; + data.start_pos = rule.s_; + } + } else { + data.duplicates_of_definition.emplace_back(name, vs.sv().data()); + } }; - struct Data { - std::shared_ptr grammar; - std::string start; - const char *start_pos = nullptr; - - std::vector> duplicates_of_definition; - - std::vector> duplicates_of_instruction; - std::map> instructions; - - std::vector> undefined_back_references; - std::vector> captures_stack{{}}; - - std::set captures_in_current_definition; - bool enablePackratParsing = true; - - Data() : grammar(std::make_shared()) {} + g["Definition"].enter = [](const Context & /*c*/, const char * /*s*/, + size_t /*n*/, std::any &dt) { + auto &data = *std::any_cast(dt); + data.captures_in_current_definition.clear(); }; - void make_grammar() { - // Setup PEG syntax parser - g["Grammar"] <= seq(g["Spacing"], oom(g["Definition"]), g["EndOfFile"]); - g["Definition"] <= - cho(seq(g["Ignore"], g["IdentCont"], g["Parameters"], g["LEFTARROW"], - g["Expression"], opt(g["Instruction"])), - seq(g["Ignore"], g["Identifier"], g["LEFTARROW"], g["Expression"], - opt(g["Instruction"]))); - g["Expression"] <= seq(g["Sequence"], zom(seq(g["SLASH"], g["Sequence"]))); - g["Sequence"] <= zom(cho(g["CUT"], g["Prefix"])); - g["Prefix"] <= seq(opt(cho(g["AND"], g["NOT"])), g["SuffixWithLabel"]); - g["SuffixWithLabel"] <= - seq(g["Suffix"], opt(seq(g["LABEL"], g["Identifier"]))); - g["Suffix"] <= seq(g["Primary"], opt(g["Loop"])); - g["Loop"] <= cho(g["QUESTION"], g["STAR"], g["PLUS"], g["Repetition"]); - g["Primary"] <= cho(seq(g["Ignore"], g["IdentCont"], g["Arguments"], - npd(g["LEFTARROW"])), - seq(g["Ignore"], g["Identifier"], - npd(seq(opt(g["Parameters"]), g["LEFTARROW"]))), - seq(g["OPEN"], g["Expression"], g["CLOSE"]), - seq(g["BeginTok"], g["Expression"], g["EndTok"]), - g["CapScope"], - seq(g["BeginCap"], g["Expression"], g["EndCap"]), - g["BackRef"], g["DictionaryI"], g["LiteralI"], - g["Dictionary"], g["Literal"], g["NegatedClassI"], - g["NegatedClass"], g["ClassI"], g["Class"], g["DOT"]); - - g["Identifier"] <= seq(g["IdentCont"], g["Spacing"]); - g["IdentCont"] <= tok(seq(g["IdentStart"], zom(g["IdentRest"]))); - - const static std::vector> range = { - {0x0080, 0xFFFF}}; - g["IdentStart"] <= seq(npd(lit(u8(u8"↑"))), npd(lit(u8(u8"⇑"))), - cho(cls("a-zA-Z_%"), cls(range))); - - g["IdentRest"] <= cho(g["IdentStart"], cls("0-9")); - - g["Dictionary"] <= seq(g["LiteralD"], oom(seq(g["PIPE"], g["LiteralD"]))); - - g["DictionaryI"] <= - seq(g["LiteralID"], oom(seq(g["PIPE"], g["LiteralID"]))); - - auto lit_ope = cho(seq(cls("'"), tok(zom(seq(npd(cls("'")), g["Char"]))), - cls("'"), g["Spacing"]), - seq(cls("\""), tok(zom(seq(npd(cls("\"")), g["Char"]))), - cls("\""), g["Spacing"])); - g["Literal"] <= lit_ope; - g["LiteralD"] <= lit_ope; - - auto lit_case_ignore_ope = - cho(seq(cls("'"), tok(zom(seq(npd(cls("'")), g["Char"]))), lit("'i"), - g["Spacing"]), - seq(cls("\""), tok(zom(seq(npd(cls("\"")), g["Char"]))), lit("\"i"), - g["Spacing"])); - g["LiteralI"] <= lit_case_ignore_ope; - g["LiteralID"] <= lit_case_ignore_ope; - - // NOTE: The original Brian Ford's paper uses 'zom' instead of 'oom'. - g["Class"] <= seq(chr('['), npd(chr('^')), - tok(oom(seq(npd(chr(']')), g["Range"]))), chr(']'), - g["Spacing"]); - g["ClassI"] <= seq(chr('['), npd(chr('^')), - tok(oom(seq(npd(chr(']')), g["Range"]))), lit("]i"), - g["Spacing"]); - - g["NegatedClass"] <= seq(lit("[^"), - tok(oom(seq(npd(chr(']')), g["Range"]))), chr(']'), - g["Spacing"]); - g["NegatedClassI"] <= seq(lit("[^"), - tok(oom(seq(npd(chr(']')), g["Range"]))), - lit("]i"), g["Spacing"]); - - // NOTE: This is different from The original Brian Ford's paper, and this - // modification allows us to specify `[+-]` as a valid char class. - g["Range"] <= - cho(seq(g["Char"], chr('-'), npd(chr(']')), g["Char"]), g["Char"]); - - g["Char"] <= - cho(seq(chr('\\'), cls("fnrtv'\"[]\\^")), - seq(chr('\\'), cls("0-3"), cls("0-7"), cls("0-7")), - seq(chr('\\'), cls("0-7"), opt(cls("0-7"))), - seq(lit("\\x"), cls("0-9a-fA-F"), opt(cls("0-9a-fA-F"))), - seq(lit("\\u"), - cho(seq(cho(seq(chr('0'), cls("0-9a-fA-F")), lit("10")), - rep(cls("0-9a-fA-F"), 4, 4)), - rep(cls("0-9a-fA-F"), 4, 5))), - seq(npd(chr('\\')), dot())); - - g["Repetition"] <= - seq(g["BeginBracket"], g["RepetitionRange"], g["EndBracket"]); - g["RepetitionRange"] <= cho(seq(g["Number"], g["COMMA"], g["Number"]), - seq(g["Number"], g["COMMA"]), g["Number"], - seq(g["COMMA"], g["Number"])); - g["Number"] <= seq(oom(cls("0-9")), g["Spacing"]); - - g["CapScope"] <= seq(g["BeginCapScope"], g["Expression"], g["EndCapScope"]); - - g["LEFTARROW"] <= seq(cho(lit("<-"), lit(u8(u8"←"))), g["Spacing"]); - ~g["SLASH"] <= seq(chr('/'), g["Spacing"]); - ~g["PIPE"] <= seq(chr('|'), g["Spacing"]); - g["AND"] <= seq(chr('&'), g["Spacing"]); - g["NOT"] <= seq(chr('!'), g["Spacing"]); - g["QUESTION"] <= seq(chr('?'), g["Spacing"]); - g["STAR"] <= seq(chr('*'), g["Spacing"]); - g["PLUS"] <= seq(chr('+'), g["Spacing"]); - ~g["OPEN"] <= seq(chr('('), g["Spacing"]); - ~g["CLOSE"] <= seq(chr(')'), g["Spacing"]); - g["DOT"] <= seq(chr('.'), g["Spacing"]); - - g["CUT"] <= seq(lit(u8(u8"↑")), g["Spacing"]); - ~g["LABEL"] <= seq(cho(chr('^'), lit(u8(u8"⇑"))), g["Spacing"]); - - ~g["Spacing"] <= zom(cho(g["Space"], g["Comment"])); - g["Comment"] <= - seq(chr('#'), zom(seq(npd(g["EndOfLine"]), dot())), g["EndOfLine"]); - g["Space"] <= cho(chr(' '), chr('\t'), g["EndOfLine"]); - g["EndOfLine"] <= cho(lit("\r\n"), chr('\n'), chr('\r')); - g["EndOfFile"] <= npd(dot()); - - ~g["BeginTok"] <= seq(chr('<'), g["Spacing"]); - ~g["EndTok"] <= seq(chr('>'), g["Spacing"]); - - ~g["BeginCapScope"] <= seq(chr('$'), chr('('), g["Spacing"]); - ~g["EndCapScope"] <= seq(chr(')'), g["Spacing"]); - - g["BeginCap"] <= seq(chr('$'), tok(g["IdentCont"]), chr('<'), g["Spacing"]); - ~g["EndCap"] <= seq(chr('>'), g["Spacing"]); - - g["BackRef"] <= seq(chr('$'), tok(g["IdentCont"]), g["Spacing"]); - - g["IGNORE"] <= chr('~'); - - g["Ignore"] <= opt(g["IGNORE"]); - g["Parameters"] <= seq(g["OPEN"], g["Identifier"], - zom(seq(g["COMMA"], g["Identifier"])), g["CLOSE"]); - g["Arguments"] <= seq(g["OPEN"], g["Expression"], - zom(seq(g["COMMA"], g["Expression"])), g["CLOSE"]); - ~g["COMMA"] <= seq(chr(','), g["Spacing"]); - - // Instruction grammars - g["Instruction"] <= - seq(g["BeginBracket"], - opt(seq(g["InstructionItem"], zom(seq(g["InstructionItemSeparator"], - g["InstructionItem"])))), - g["EndBracket"]); - g["InstructionItem"] <= - cho(g["PrecedenceClimbing"], g["ErrorMessage"], g["NoAstOpt"]); - ~g["InstructionItemSeparator"] <= seq(chr(';'), g["Spacing"]); - - ~g["SpacesZom"] <= zom(g["Space"]); - ~g["SpacesOom"] <= oom(g["Space"]); - ~g["BeginBracket"] <= seq(chr('{'), g["Spacing"]); - ~g["EndBracket"] <= seq(chr('}'), g["Spacing"]); - - // PrecedenceClimbing instruction - g["PrecedenceClimbing"] <= - seq(lit("precedence"), g["SpacesOom"], g["PrecedenceInfo"], - zom(seq(g["SpacesOom"], g["PrecedenceInfo"])), g["SpacesZom"]); - g["PrecedenceInfo"] <= - seq(g["PrecedenceAssoc"], - oom(seq(ign(g["SpacesOom"]), g["PrecedenceOpe"]))); - g["PrecedenceOpe"] <= - cho(seq(cls("'"), - tok(zom(seq(npd(cho(g["Space"], cls("'"))), g["Char"]))), - cls("'")), - seq(cls("\""), - tok(zom(seq(npd(cho(g["Space"], cls("\""))), g["Char"]))), - cls("\"")), - tok(oom(seq(npd(cho(g["PrecedenceAssoc"], g["Space"], chr('}'))), - dot())))); - g["PrecedenceAssoc"] <= cls("LR"); - - // Error message instruction - g["ErrorMessage"] <= seq(lit("error_message"), g["SpacesOom"], - g["LiteralD"], g["SpacesZom"]); - - // No Ast node optimization instruction - g["NoAstOpt"] <= seq(lit("no_ast_opt"), g["SpacesZom"]); - - // Set definition names - for (auto &x : g) { - x.second.name = x.first; + g["Expression"] = [&](const SemanticValues &vs) { + if (vs.size() == 1) { + return std::any_cast>(vs[0]); + } else { + std::vector> opes; + for (auto i = 0u; i < vs.size(); i++) { + opes.emplace_back(std::any_cast>(vs[i])); } - } + const std::shared_ptr ope = + std::make_shared(opes); + return ope; + } + }; - void setup_actions() { - g["Definition"] = [&](const SemanticValues &vs, std::any &dt) { - auto &data = *std::any_cast(dt); - - auto is_macro = vs.choice() == 0; - auto ignore = std::any_cast(vs[0]); - auto name = std::any_cast(vs[1]); - - std::vector params; - std::shared_ptr ope; - auto has_instructions = false; - - if (is_macro) { - params = std::any_cast>(vs[2]); - ope = std::any_cast>(vs[4]); - if (vs.size() == 6) { has_instructions = true; } - } else { - ope = std::any_cast>(vs[3]); - if (vs.size() == 5) { has_instructions = true; } - } - - if (has_instructions) { - auto index = is_macro ? 5 : 4; - std::unordered_set types; - for (const auto &instruction : - std::any_cast>(vs[index])) { - const auto &type = instruction.type; - if (types.find(type) == types.end()) { - data.instructions[name].push_back(instruction); - types.insert(instruction.type); - if (type == "declare_symbol" || type == "check_symbol") { - if (!TokenChecker::is_token(*ope)) { ope = tok(ope); } - } - } else { - data.duplicates_of_instruction.emplace_back(type, - instruction.sv.data()); - } - } - } - - auto &grammar = *data.grammar; - if (!grammar.count(name)) { - auto &rule = grammar[name]; - rule <= ope; - rule.name = name; - rule.s_ = vs.sv().data(); - rule.line_ = line_info(vs.ss, rule.s_); - rule.ignoreSemanticValue = ignore; - rule.is_macro = is_macro; - rule.params = params; - - if (data.start.empty()) { - data.start = rule.name; - data.start_pos = rule.s_; - } - } else { - data.duplicates_of_definition.emplace_back(name, vs.sv().data()); - } - }; - - g["Definition"].enter = [](const Context & /*c*/, const char * /*s*/, - size_t /*n*/, std::any &dt) { - auto &data = *std::any_cast(dt); - data.captures_in_current_definition.clear(); - }; - - g["Expression"] = [&](const SemanticValues &vs) { - if (vs.size() == 1) { - return std::any_cast>(vs[0]); - } else { - std::vector> opes; - for (auto i = 0u; i < vs.size(); i++) { - opes.emplace_back(std::any_cast>(vs[i])); - } - const std::shared_ptr ope = - std::make_shared(opes); - return ope; - } - }; - - g["Sequence"] = [&](const SemanticValues &vs) { - if (vs.empty()) { - return npd(lit("")); - } else if (vs.size() == 1) { - return std::any_cast>(vs[0]); - } else { - std::vector> opes; - for (const auto &x : vs) { - opes.emplace_back(std::any_cast>(x)); - } - const std::shared_ptr ope = std::make_shared(opes); - return ope; - } - }; - - g["Prefix"] = [&](const SemanticValues &vs) { - std::shared_ptr ope; - if (vs.size() == 1) { - ope = std::any_cast>(vs[0]); - } else { - assert(vs.size() == 2); - auto tok = std::any_cast(vs[0]); - ope = std::any_cast>(vs[1]); - if (tok == '&') { - ope = apd(ope); - } else { // '!' - ope = npd(ope); - } - } - return ope; - }; - - g["SuffixWithLabel"] = [&](const SemanticValues &vs, std::any &dt) { - auto ope = std::any_cast>(vs[0]); - if (vs.size() == 1) { - return ope; - } else { - assert(vs.size() == 2); - auto &data = *std::any_cast(dt); - const auto &ident = std::any_cast(vs[1]); - auto label = ref(*data.grammar, ident, vs.sv().data(), false, {}); - auto recovery = rec(ref(*data.grammar, RECOVER_DEFINITION_NAME, - vs.sv().data(), true, {label})); - return cho4label_(ope, recovery); - } - }; - - struct Loop { - enum class Type { opt = 0, zom, oom, rep }; - Type type; - std::pair range; - }; - - g["Suffix"] = [&](const SemanticValues &vs) { - auto ope = std::any_cast>(vs[0]); - if (vs.size() == 1) { - return ope; - } else { - assert(vs.size() == 2); - auto loop = std::any_cast(vs[1]); - switch (loop.type) { - case Loop::Type::opt: return opt(ope); - case Loop::Type::zom: return zom(ope); - case Loop::Type::oom: return oom(ope); - default: // Regex-like repetition - return rep(ope, loop.range.first, loop.range.second); - } - } - }; - - g["Loop"] = [&](const SemanticValues &vs) { - switch (vs.choice()) { - case 0: // Option - return Loop{Loop::Type::opt, std::pair()}; - case 1: // Zero or More - return Loop{Loop::Type::zom, std::pair()}; - case 2: // One or More - return Loop{Loop::Type::oom, std::pair()}; - default: // Regex-like repetition - return Loop{Loop::Type::rep, - std::any_cast>(vs[0])}; - } - }; - - g["Primary"] = [&](const SemanticValues &vs, std::any &dt) { - auto &data = *std::any_cast(dt); - - switch (vs.choice()) { - case 0: // Macro Reference - case 1: { // Reference - auto is_macro = vs.choice() == 0; - auto ignore = std::any_cast(vs[0]); - const auto &ident = std::any_cast(vs[1]); - - std::vector> args; - if (is_macro) { - args = std::any_cast>>(vs[2]); - } - - auto ope = ref(*data.grammar, ident, vs.sv().data(), is_macro, args); - if (ident == RECOVER_DEFINITION_NAME) { ope = rec(ope); } - - if (ignore) { - return ign(ope); - } else { - return ope; - } - } - case 2: { // (Expression) - return std::any_cast>(vs[0]); - } - case 3: { // TokenBoundary - return tok(std::any_cast>(vs[0])); - } - case 4: { // CaptureScope - return csc(std::any_cast>(vs[0])); - } - case 5: { // Capture - const auto &name = std::any_cast(vs[0]); - auto ope = std::any_cast>(vs[1]); - - data.captures_stack.back().insert(name); - data.captures_in_current_definition.insert(name); - - return cap(ope, [name](const char *a_s, size_t a_n, Context &c) { - auto &cs = c.capture_scope_stack[c.capture_scope_stack_size - 1]; - cs[name] = std::string(a_s, a_n); - }); - } - default: { - return std::any_cast>(vs[0]); - } - } - }; - - g["IdentCont"] = [](const SemanticValues &vs) { - return std::string(vs.sv().data(), vs.sv().length()); - }; - - g["Dictionary"] = [](const SemanticValues &vs) { - auto items = vs.transform(); - return dic(items, false); - }; - g["DictionaryI"] = [](const SemanticValues &vs) { - auto items = vs.transform(); - return dic(items, true); - }; - - g["Literal"] = [](const SemanticValues &vs) { - const auto &tok = vs.tokens.front(); - return lit(resolve_escape_sequence(tok.data(), tok.size())); - }; - g["LiteralI"] = [](const SemanticValues &vs) { - const auto &tok = vs.tokens.front(); - return liti(resolve_escape_sequence(tok.data(), tok.size())); - }; - g["LiteralD"] = [](const SemanticValues &vs) { - auto &tok = vs.tokens.front(); - return resolve_escape_sequence(tok.data(), tok.size()); - }; - g["LiteralID"] = [](const SemanticValues &vs) { - auto &tok = vs.tokens.front(); - return resolve_escape_sequence(tok.data(), tok.size()); - }; - - g["Class"] = [](const SemanticValues &vs) { - auto ranges = vs.transform>(); - return cls(ranges); - }; - g["ClassI"] = [](const SemanticValues &vs) { - auto ranges = vs.transform>(); - return cls(ranges, true); - }; - g["NegatedClass"] = [](const SemanticValues &vs) { - auto ranges = vs.transform>(); - return ncls(ranges); - }; - g["NegatedClassI"] = [](const SemanticValues &vs) { - auto ranges = vs.transform>(); - return ncls(ranges, true); - }; - g["Range"] = [](const SemanticValues &vs) { - switch (vs.choice()) { - case 0: { - auto s1 = std::any_cast(vs[0]); - auto s2 = std::any_cast(vs[1]); - auto cp1 = decode_codepoint(s1.data(), s1.length()); - auto cp2 = decode_codepoint(s2.data(), s2.length()); - return std::pair(cp1, cp2); - } - case 1: { - auto s = std::any_cast(vs[0]); - auto cp = decode_codepoint(s.data(), s.length()); - return std::pair(cp, cp); - } - } - return std::pair(0, 0); - }; - g["Char"] = [](const SemanticValues &vs) { - return resolve_escape_sequence(vs.sv().data(), vs.sv().length()); - }; - - g["RepetitionRange"] = [&](const SemanticValues &vs) { - switch (vs.choice()) { - case 0: { // Number COMMA Number - auto min = std::any_cast(vs[0]); - auto max = std::any_cast(vs[1]); - return std::pair(min, max); - } - case 1: // Number COMMA - return std::pair(std::any_cast(vs[0]), - std::numeric_limits::max()); - case 2: { // Number - auto n = std::any_cast(vs[0]); - return std::pair(n, n); - } - default: // COMMA Number - return std::pair(std::numeric_limits::min(), - std::any_cast(vs[0])); - } - }; - g["Number"] = [&](const SemanticValues &vs) { - return vs.token_to_number(); - }; - - g["CapScope"].enter = [](const Context & /*c*/, const char * /*s*/, - size_t /*n*/, std::any &dt) { - auto &data = *std::any_cast(dt); - data.captures_stack.emplace_back(); - }; - g["CapScope"].leave = [](const Context & /*c*/, const char * /*s*/, - size_t /*n*/, size_t /*matchlen*/, - std::any & /*value*/, std::any &dt) { - auto &data = *std::any_cast(dt); - data.captures_stack.pop_back(); - }; - - g["AND"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - g["NOT"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - g["QUESTION"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - g["STAR"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - g["PLUS"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - - g["DOT"] = [](const SemanticValues & /*vs*/) { return dot(); }; - - g["CUT"] = [](const SemanticValues & /*vs*/) { return cut(); }; - - g["BeginCap"] = [](const SemanticValues &vs) { return vs.token(); }; - - g["BackRef"] = [&](const SemanticValues &vs, std::any &dt) { - auto &data = *std::any_cast(dt); - - // Undefined back reference check - { - auto found = false; - auto it = data.captures_stack.rbegin(); - while (it != data.captures_stack.rend()) { - if (it->find(vs.token()) != it->end()) { - found = true; - break; - } - ++it; - } - if (!found) { - auto ptr = vs.token().data() - 1; // include '$' symbol - data.undefined_back_references.emplace_back(vs.token(), ptr); - } - } - - // NOTE: Disable packrat parsing if a back reference is not defined in - // captures in the current definition rule. - if (data.captures_in_current_definition.find(vs.token()) == - data.captures_in_current_definition.end()) { - data.enablePackratParsing = false; - } - - return bkr(vs.token_to_string()); - }; - - g["Ignore"] = [](const SemanticValues &vs) { return vs.size() > 0; }; - - g["Parameters"] = [](const SemanticValues &vs) { - return vs.transform(); - }; - - g["Arguments"] = [](const SemanticValues &vs) { - return vs.transform>(); - }; - - g["PrecedenceClimbing"] = [](const SemanticValues &vs) { - PrecedenceClimbing::BinOpeInfo binOpeInfo; - size_t level = 1; - for (auto v : vs) { - auto tokens = std::any_cast>(v); - auto assoc = tokens[0][0]; - for (size_t i = 1; i < tokens.size(); i++) { - binOpeInfo[tokens[i]] = std::pair(level, assoc); - } - level++; - } - Instruction instruction; - instruction.type = "precedence"; - instruction.data = binOpeInfo; - instruction.sv = vs.sv(); - return instruction; - }; - g["PrecedenceInfo"] = [](const SemanticValues &vs) { - return vs.transform(); - }; - g["PrecedenceOpe"] = [](const SemanticValues &vs) { return vs.token(); }; - g["PrecedenceAssoc"] = [](const SemanticValues &vs) { return vs.token(); }; - - g["ErrorMessage"] = [](const SemanticValues &vs) { - Instruction instruction; - instruction.type = "error_message"; - instruction.data = std::any_cast(vs[0]); - instruction.sv = vs.sv(); - return instruction; - }; - - g["NoAstOpt"] = [](const SemanticValues &vs) { - Instruction instruction; - instruction.type = "no_ast_opt"; - instruction.sv = vs.sv(); - return instruction; - }; - - g["Instruction"] = [](const SemanticValues &vs) { - return vs.transform(); - }; - } - - bool apply_precedence_instruction(Definition &rule, - const PrecedenceClimbing::BinOpeInfo &info, - const char *s, Log log) { - try { - auto &seq = dynamic_cast(*rule.get_core_operator()); - auto atom = seq.opes_[0]; - auto &rep = dynamic_cast(*seq.opes_[1]); - auto &seq1 = dynamic_cast(*rep.ope_); - auto binop = seq1.opes_[0]; - auto atom1 = seq1.opes_[1]; - - auto atom_name = dynamic_cast(*atom).name_; - auto binop_name = dynamic_cast(*binop).name_; - auto atom1_name = dynamic_cast(*atom1).name_; - - if (!rep.is_zom() || atom_name != atom1_name || atom_name == binop_name) { - if (log) { - auto line = line_info(s, rule.s_); - log(line.first, line.second, - "'precedence' instruction cannot be applied to '" + rule.name + - "'.", - ""); - } - return false; - } - - rule.holder_->ope_ = pre(atom, binop, info, rule); - rule.disable_action = true; - } catch (...) { - if (log) { - auto line = line_info(s, rule.s_); - log(line.first, line.second, - "'precedence' instruction cannot be applied to '" + rule.name + - "'.", - ""); - } - return false; + g["Sequence"] = [&](const SemanticValues &vs) { + if (vs.empty()) { + return npd(lit("")); + } else if (vs.size() == 1) { + return std::any_cast>(vs[0]); + } else { + std::vector> opes; + for (const auto &x : vs) { + opes.emplace_back(std::any_cast>(x)); } - return true; - } + const std::shared_ptr ope = std::make_shared(opes); + return ope; + } + }; - ParserContext perform_core(const char *s, size_t n, const Rules &rules, - Log log, std::string requested_start) { - Data data; - auto &grammar = *data.grammar; + g["Prefix"] = [&](const SemanticValues &vs) { + std::shared_ptr ope; + if (vs.size() == 1) { + ope = std::any_cast>(vs[0]); + } else { + assert(vs.size() == 2); + auto tok = std::any_cast(vs[0]); + ope = std::any_cast>(vs[1]); + if (tok == '&') { + ope = apd(ope); + } else { // '!' + ope = npd(ope); + } + } + return ope; + }; - // Built-in macros - { - // `%recover` - { - auto &rule = grammar[RECOVER_DEFINITION_NAME]; - rule <= ref(grammar, "x", "", false, {}); - rule.name = RECOVER_DEFINITION_NAME; - rule.s_ = "[native]"; - rule.ignoreSemanticValue = true; - rule.is_macro = true; - rule.params = {"x"}; - } + g["SuffixWithLabel"] = [&](const SemanticValues &vs, std::any &dt) { + auto ope = std::any_cast>(vs[0]); + if (vs.size() == 1) { + return ope; + } else { + assert(vs.size() == 2); + auto &data = *std::any_cast(dt); + const auto &ident = std::any_cast(vs[1]); + auto label = ref(*data.grammar, ident, vs.sv().data(), false, {}); + auto recovery = rec(ref(*data.grammar, RECOVER_DEFINITION_NAME, + vs.sv().data(), true, {label})); + return cho4label_(ope, recovery); + } + }; + + struct Loop { + enum class Type { opt = 0, zom, oom, rep }; + Type type; + std::pair range; + }; + + g["Suffix"] = [&](const SemanticValues &vs) { + auto ope = std::any_cast>(vs[0]); + if (vs.size() == 1) { + return ope; + } else { + assert(vs.size() == 2); + auto loop = std::any_cast(vs[1]); + switch (loop.type) { + case Loop::Type::opt: return opt(ope); + case Loop::Type::zom: return zom(ope); + case Loop::Type::oom: return oom(ope); + default: // Regex-like repetition + return rep(ope, loop.range.first, loop.range.second); + } + } + }; + + g["Loop"] = [&](const SemanticValues &vs) { + switch (vs.choice()) { + case 0: // Option + return Loop{Loop::Type::opt, std::pair()}; + case 1: // Zero or More + return Loop{Loop::Type::zom, std::pair()}; + case 2: // One or More + return Loop{Loop::Type::oom, std::pair()}; + default: // Regex-like repetition + return Loop{Loop::Type::rep, + std::any_cast>(vs[0])}; + } + }; + + g["Primary"] = [&](const SemanticValues &vs, std::any &dt) { + auto &data = *std::any_cast(dt); + + switch (vs.choice()) { + case 0: // Macro Reference + case 1: { // Reference + auto is_macro = vs.choice() == 0; + auto ignore = std::any_cast(vs[0]); + const auto &ident = std::any_cast(vs[1]); + + std::vector> args; + if (is_macro) { + args = std::any_cast>>(vs[2]); } - std::any dt = &data; - auto r = g["Grammar"].parse(s, n, dt, nullptr, log); + auto ope = ref(*data.grammar, ident, vs.sv().data(), is_macro, args); + if (ident == RECOVER_DEFINITION_NAME) { ope = rec(ope); } - if (!r.ret) { - if (log) { - if (r.error_info.message_pos) { - auto line = line_info(s, r.error_info.message_pos); - log(line.first, line.second, r.error_info.message, - r.error_info.label); - } else { - auto line = line_info(s, r.error_info.error_pos); - log(line.first, line.second, "syntax error", r.error_info.label); - } - } - return {}; + if (ignore) { + return ign(ope); + } else { + return ope; } + } + case 2: { // (Expression) + return std::any_cast>(vs[0]); + } + case 3: { // TokenBoundary + return tok(std::any_cast>(vs[0])); + } + case 4: { // CaptureScope + return csc(std::any_cast>(vs[0])); + } + case 5: { // Capture + const auto &name = std::any_cast(vs[0]); + auto ope = std::any_cast>(vs[1]); - // User provided rules - for (auto [user_name, user_rule] : rules) { - auto name = user_name; - auto ignore = false; - if (!name.empty() && name[0] == '~') { - ignore = true; - name.erase(0, 1); - } - if (!name.empty()) { - auto &rule = grammar[name]; - rule <= user_rule; - rule.name = name; - rule.ignoreSemanticValue = ignore; - } + data.captures_stack.back().insert(name); + data.captures_in_current_definition.insert(name); + + return cap(ope, [name](const char *a_s, size_t a_n, Context &c) { + c.capture_entries.emplace_back(name, std::string(a_s, a_n)); + }); + } + default: { + return std::any_cast>(vs[0]); + } + } + }; + + g["IdentCont"] = [](const SemanticValues &vs) { + return std::string(vs.sv().data(), vs.sv().length()); + }; + + g["Dictionary"] = [](const SemanticValues &vs) { + auto items = vs.transform(); + return dic(items, false); + }; + g["DictionaryI"] = [](const SemanticValues &vs) { + auto items = vs.transform(); + return dic(items, true); + }; + + g["Literal"] = [](const SemanticValues &vs) { + const auto &tok = vs.tokens.front(); + return lit(resolve_escape_sequence(tok.data(), tok.size())); + }; + g["LiteralI"] = [](const SemanticValues &vs) { + const auto &tok = vs.tokens.front(); + return liti(resolve_escape_sequence(tok.data(), tok.size())); + }; + g["LiteralD"] = [](const SemanticValues &vs) { + auto &tok = vs.tokens.front(); + return resolve_escape_sequence(tok.data(), tok.size()); + }; + g["LiteralID"] = [](const SemanticValues &vs) { + auto &tok = vs.tokens.front(); + return resolve_escape_sequence(tok.data(), tok.size()); + }; + + g["Class"] = [](const SemanticValues &vs) { + auto ranges = vs.transform>(); + return cls(ranges); + }; + g["ClassI"] = [](const SemanticValues &vs) { + auto ranges = vs.transform>(); + return cls(ranges, true); + }; + g["NegatedClass"] = [](const SemanticValues &vs) { + auto ranges = vs.transform>(); + return ncls(ranges); + }; + g["NegatedClassI"] = [](const SemanticValues &vs) { + auto ranges = vs.transform>(); + return ncls(ranges, true); + }; + g["Range"] = [](const SemanticValues &vs) { + switch (vs.choice()) { + case 0: { + auto s1 = std::any_cast(vs[0]); + auto s2 = std::any_cast(vs[1]); + auto cp1 = decode_codepoint(s1.data(), s1.length()); + auto cp2 = decode_codepoint(s2.data(), s2.length()); + if (cp1 > cp2) { + throw SyntaxErrorException("characer range is out of order...", + vs.line_info()); } + return std::pair(cp1, cp2); + } + case 1: { + auto s = std::any_cast(vs[0]); + auto cp = decode_codepoint(s.data(), s.length()); + return std::pair(cp, cp); + } + } + return std::pair(0, 0); + }; + g["Char"] = [](const SemanticValues &vs) { + return resolve_escape_sequence(vs.sv().data(), vs.sv().length()); + }; - // Check duplicated definitions - auto ret = true; + g["RepetitionRange"] = [&](const SemanticValues &vs) { + switch (vs.choice()) { + case 0: { // Number COMMA Number + auto min = std::any_cast(vs[0]); + auto max = std::any_cast(vs[1]); + return std::pair(min, max); + } + case 1: // Number COMMA + return std::pair(std::any_cast(vs[0]), + std::numeric_limits::max()); + case 2: { // Number + auto n = std::any_cast(vs[0]); + return std::pair(n, n); + } + default: // COMMA Number + return std::pair(std::numeric_limits::min(), + std::any_cast(vs[0])); + } + }; + g["Number"] = [&](const SemanticValues &vs) { + return vs.token_to_number(); + }; - if (!data.duplicates_of_definition.empty()) { - for (const auto &[name, ptr] : data.duplicates_of_definition) { - if (log) { - auto line = line_info(s, ptr); - log(line.first, line.second, - "The definition '" + name + "' is already defined.", ""); - } - } - ret = false; + g["CapScope"].enter = [](const Context & /*c*/, const char * /*s*/, + size_t /*n*/, std::any &dt) { + auto &data = *std::any_cast(dt); + data.captures_stack.emplace_back(); + }; + g["CapScope"].leave = [](const Context & /*c*/, const char * /*s*/, + size_t /*n*/, size_t /*matchlen*/, + std::any & /*value*/, std::any &dt) { + auto &data = *std::any_cast(dt); + data.captures_stack.pop_back(); + }; + + g["AND"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + g["NOT"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + g["QUESTION"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + g["STAR"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + g["PLUS"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + + g["DOT"] = [](const SemanticValues & /*vs*/) { return dot(); }; + + g["CUT"] = [](const SemanticValues & /*vs*/) { return cut(); }; + + g["BeginCap"] = [](const SemanticValues &vs) { return vs.token(); }; + + g["BackRef"] = [&](const SemanticValues &vs, std::any &dt) { + auto &data = *std::any_cast(dt); + + // Undefined back reference check + { + auto found = false; + auto it = data.captures_stack.rbegin(); + while (it != data.captures_stack.rend()) { + if (it->find(vs.token()) != it->end()) { + found = true; + break; + } + ++it; } - - // Check duplicated instructions - if (!data.duplicates_of_instruction.empty()) { - for (const auto &[type, ptr] : data.duplicates_of_instruction) { - if (log) { - auto line = line_info(s, ptr); - log(line.first, line.second, - "The instruction '" + type + "' is already defined.", ""); - } - } - ret = false; + if (!found) { + auto ptr = vs.token().data() - 1; // include '$' symbol + data.undefined_back_references.emplace_back(vs.token(), ptr); } + } - // Check undefined back references - if (!data.undefined_back_references.empty()) { - for (const auto &[name, ptr] : data.undefined_back_references) { - if (log) { - auto line = line_info(s, ptr); - log(line.first, line.second, - "The back reference '" + name + "' is undefined.", ""); - } - } - ret = false; + // NOTE: Disable packrat parsing if a back reference is not defined in + // captures in the current definition rule. + if (data.captures_in_current_definition.find(vs.token()) == + data.captures_in_current_definition.end()) { + data.enablePackratParsing = false; + } + + return bkr(vs.token_to_string()); + }; + + g["Ignore"] = [](const SemanticValues &vs) { return vs.size() > 0; }; + + g["Parameters"] = [](const SemanticValues &vs) { + return vs.transform(); + }; + + g["Arguments"] = [](const SemanticValues &vs) { + return vs.transform>(); + }; + + g["PrecedenceClimbing"] = [](const SemanticValues &vs) { + PrecedenceClimbing::BinOpeInfo binOpeInfo; + size_t level = 1; + for (const auto &v : vs) { + auto tokens = std::any_cast>(v); + auto assoc = tokens[0][0]; + for (size_t i = 1; i < tokens.size(); i++) { + binOpeInfo[tokens[i]] = std::pair(level, assoc); } + level++; + } + Instruction instruction; + instruction.type = "precedence"; + instruction.data = binOpeInfo; + instruction.sv = vs.sv(); + return instruction; + }; + g["PrecedenceInfo"] = [](const SemanticValues &vs) { + return vs.transform(); + }; + g["PrecedenceOpe"] = [](const SemanticValues &vs) { return vs.token(); }; + g["PrecedenceAssoc"] = [](const SemanticValues &vs) { return vs.token(); }; - // Set root definition - auto start = data.start; + g["ErrorMessage"] = [](const SemanticValues &vs) { + Instruction instruction; + instruction.type = "error_message"; + instruction.data = std::any_cast(vs[0]); + instruction.sv = vs.sv(); + return instruction; + }; - if (!requested_start.empty()) { - if (grammar.count(requested_start)) { - start = requested_start; - } else { - if (log) { - auto line = line_info(s, s); - log(line.first, line.second, - "The specified start rule '" + requested_start + - "' is undefined.", - ""); - } - ret = false; - } - } + g["NoAstOpt"] = [](const SemanticValues &vs) { + Instruction instruction; + instruction.type = "no_ast_opt"; + instruction.sv = vs.sv(); + return instruction; + }; - if (!ret) { return {}; } + g["Instruction"] = [](const SemanticValues &vs) { + return vs.transform(); + }; + } - auto &start_rule = grammar[start]; + bool apply_precedence_instruction(Definition &rule, + const PrecedenceClimbing::BinOpeInfo &info, + const char *s, Log log) { + try { + auto &seq = dynamic_cast(*rule.get_core_operator()); + auto atom = seq.opes_[0]; + auto &rep = dynamic_cast(*seq.opes_[1]); + auto &seq1 = dynamic_cast(*rep.ope_); + auto binop = seq1.opes_[0]; + auto atom1 = seq1.opes_[1]; - // Check if the start rule has ignore operator - { - if (start_rule.ignoreSemanticValue) { - if (log) { - auto line = line_info(s, start_rule.s_); - log(line.first, line.second, - "Ignore operator cannot be applied to '" + start_rule.name + "'.", - ""); - } - ret = false; - } - } + auto atom_name = dynamic_cast(*atom).name_; + auto binop_name = dynamic_cast(*binop).name_; + auto atom1_name = dynamic_cast(*atom1).name_; - if (!ret) { return {}; } - - // Check missing definitions - auto referenced = std::unordered_set{ - WHITESPACE_DEFINITION_NAME, - WORD_DEFINITION_NAME, - RECOVER_DEFINITION_NAME, - start_rule.name, - }; - - for (auto &[_, rule] : grammar) { - ReferenceChecker vis(grammar, rule.params); - rule.accept(vis); - referenced.insert(vis.referenced.begin(), vis.referenced.end()); - for (const auto &[name, ptr] : vis.error_s) { - if (log) { - auto line = line_info(s, ptr); - log(line.first, line.second, vis.error_message[name], ""); - } - ret = false; - } - } - - for (auto &[name, rule] : grammar) { - if (!referenced.count(name)) { - if (log) { - auto line = line_info(s, rule.s_); - auto msg = "'" + name + "' is not referenced."; - log(line.first, line.second, msg, ""); - } - } - } - - if (!ret) { return {}; } - - // Link references - for (auto &x : grammar) { - auto &rule = x.second; - LinkReferences vis(grammar, rule.params); - rule.accept(vis); - } - - // Check left recursion - ret = true; - - for (auto &[name, rule] : grammar) { - DetectLeftRecursion vis(name); - rule.accept(vis); - if (vis.error_s) { - if (log) { - auto line = line_info(s, vis.error_s); - log(line.first, line.second, "'" + name + "' is left recursive.", ""); - } - ret = false; - } - } - - if (!ret) { return {}; } - - // Check infinite loop - if (detect_infiniteLoop(data, start_rule, log, s)) { return {}; } - - // Automatic whitespace skipping - if (grammar.count(WHITESPACE_DEFINITION_NAME)) { - for (auto &x : grammar) { - auto &rule = x.second; - auto ope = rule.get_core_operator(); - if (IsLiteralToken::check(*ope)) { rule <= tok(ope); } - } - - auto &rule = grammar[WHITESPACE_DEFINITION_NAME]; - start_rule.whitespaceOpe = wsp(rule.get_core_operator()); - - if (detect_infiniteLoop(data, rule, log, s)) { return {}; } - } - - // Word expression - if (grammar.count(WORD_DEFINITION_NAME)) { - auto &rule = grammar[WORD_DEFINITION_NAME]; - start_rule.wordOpe = rule.get_core_operator(); - - if (detect_infiniteLoop(data, rule, log, s)) { return {}; } - } - - // Apply instructions - for (const auto &[name, instructions] : data.instructions) { - auto &rule = grammar[name]; - - for (const auto &instruction : instructions) { - if (instruction.type == "precedence") { - const auto &info = - std::any_cast(instruction.data); - - if (!apply_precedence_instruction(rule, info, s, log)) { return {}; } - } else if (instruction.type == "error_message") { - rule.error_message = std::any_cast(instruction.data); - } else if (instruction.type == "no_ast_opt") { - rule.no_ast_opt = true; - } - } - } - - return {data.grammar, start, data.enablePackratParsing}; - } - - bool detect_infiniteLoop(const Data &data, Definition &rule, const Log &log, - const char *s) const { - std::vector> refs; - std::unordered_map has_error_cache; - DetectInfiniteLoop vis(data.start_pos, rule.name, refs, has_error_cache); - rule.accept(vis); - if (vis.has_error) { - if (log) { - auto line = line_info(s, vis.error_s); - log(line.first, line.second, - "infinite loop is detected in '" + vis.error_name + "'.", ""); - } - return true; + if (!rep.is_zom() || atom_name != atom1_name || atom_name == binop_name) { + if (log) { + auto line = line_info(s, rule.s_); + log(line.first, line.second, + "'precedence' instruction cannot be applied to '" + rule.name + + "'.", + ""); } return false; + } + + rule.holder_->ope_ = pre(atom, binop, info, rule); + rule.disable_action = true; + } catch (...) { + if (log) { + auto line = line_info(s, rule.s_); + log(line.first, line.second, + "'precedence' instruction cannot be applied to '" + rule.name + + "'.", + ""); + } + return false; + } + return true; + } + + ParserContext perform_core(const char *s, size_t n, const Rules &rules, + Log log, std::string requested_start, + bool enable_left_recursion = true) { + Data data; + auto &grammar = *data.grammar; + + // Built-in macros + { + // `%recover` + { + auto &rule = grammar[RECOVER_DEFINITION_NAME]; + rule <= ref(grammar, "x", "", false, {}); + rule.name = RECOVER_DEFINITION_NAME; + rule.s_ = "[native]"; + rule.ignoreSemanticValue = true; + rule.is_macro = true; + rule.params = {"x"}; + } } - Grammar g; + try { + std::any dt = &data; + auto r = g["Grammar"].parse(s, n, dt, nullptr, log); + + if (!r.ret) { + if (log) { + if (r.error_info.message_pos) { + auto line = line_info(s, r.error_info.message_pos); + log(line.first, line.second, r.error_info.message, + r.error_info.label); + } else { + auto line = line_info(s, r.error_info.error_pos); + log(line.first, line.second, "syntax error", r.error_info.label); + } + } + return {}; + } + } catch (const SyntaxErrorException &e) { + if (log) { + auto line = e.line_info(); + log(line.first, line.second, e.what(), ""); + } + return {}; + } + + // User provided rules + for (auto [user_name, user_rule] : rules) { + auto name = user_name; + auto ignore = false; + if (!name.empty() && name[0] == '~') { + ignore = true; + name.erase(0, 1); + } + if (!name.empty()) { + auto &rule = grammar[name]; + rule <= user_rule; + rule.name = name; + rule.ignoreSemanticValue = ignore; + } + } + + // Check duplicated definitions + auto ret = true; + + if (!data.duplicates_of_definition.empty()) { + for (const auto &[name, ptr] : data.duplicates_of_definition) { + if (log) { + auto line = line_info(s, ptr); + log(line.first, line.second, + "the definition '" + name + "' is already defined.", ""); + } + } + ret = false; + } + + // Check duplicated instructions + if (!data.duplicates_of_instruction.empty()) { + for (const auto &[type, ptr] : data.duplicates_of_instruction) { + if (log) { + auto line = line_info(s, ptr); + log(line.first, line.second, + "the instruction '" + type + "' is already defined.", ""); + } + } + ret = false; + } + + // Check undefined back references + if (!data.undefined_back_references.empty()) { + for (const auto &[name, ptr] : data.undefined_back_references) { + if (log) { + auto line = line_info(s, ptr); + log(line.first, line.second, + "the back reference '" + name + "' is undefined.", ""); + } + } + ret = false; + } + + // Set root definition + auto start = data.start; + + if (!requested_start.empty()) { + if (grammar.count(requested_start)) { + start = requested_start; + } else { + if (log) { + auto line = line_info(s, s); + log(line.first, line.second, + "the specified start rule '" + requested_start + + "' is undefined.", + ""); + } + ret = false; + } + } + + if (!ret) { return {}; } + + auto &start_rule = grammar[start]; + + // Check if the start rule has ignore operator + { + if (start_rule.ignoreSemanticValue) { + if (log) { + auto line = line_info(s, start_rule.s_); + log(line.first, line.second, + "ignore operator cannot be applied to '" + start_rule.name + "'.", + ""); + } + ret = false; + } + } + + if (!ret) { return {}; } + + // Check missing definitions + auto referenced = std::unordered_set{ + WHITESPACE_DEFINITION_NAME, + WORD_DEFINITION_NAME, + RECOVER_DEFINITION_NAME, + start_rule.name, + }; + + for (auto &[_, rule] : grammar) { + ReferenceChecker vis(grammar, rule.params); + rule.accept(vis); + referenced.insert(vis.referenced.begin(), vis.referenced.end()); + for (const auto &[name, ptr] : vis.error_s) { + if (log) { + auto line = line_info(s, ptr); + log(line.first, line.second, vis.error_message[name], ""); + } + ret = false; + } + } + + for (auto &[name, rule] : grammar) { + if (!referenced.count(name)) { + if (log) { + auto line = line_info(s, rule.s_); + auto msg = "'" + name + "' is not referenced."; + log(line.first, line.second, msg, ""); + } + } + } + + if (!ret) { return {}; } + + // Link references + for (auto &x : grammar) { + auto &rule = x.second; + LinkReferences vis(grammar, rule.params); + rule.accept(vis); + } + + // Compute can_be_empty for each rule (fixed-point iteration) + { + bool changed = true; + while (changed) { + changed = false; + for (auto &[name, rule] : grammar) { + ComputeCanBeEmpty vis; + rule.accept(vis); + if (vis.result != rule.can_be_empty) { + rule.can_be_empty = vis.result; + changed = true; + } + } + } + } + + // Check left recursion + if (enable_left_recursion) { + for (auto &[name, rule] : grammar) { + DetectLeftRecursion vis(name); + rule.accept(vis); + if (vis.error_s) { rule.is_left_recursive = true; } + } + } else { + ret = true; + + for (auto &[name, rule] : grammar) { + DetectLeftRecursion vis(name); + rule.accept(vis); + if (vis.error_s) { + if (log) { + auto line = line_info(s, vis.error_s); + log(line.first, line.second, "'" + name + "' is left recursive.", + ""); + } + ret = false; + } + } + + if (!ret) { return {}; } + } + + // Check infinite loop + if (detect_infiniteLoop(data, start_rule, log, s)) { return {}; } + + // Automatic whitespace skipping + if (grammar.count(WHITESPACE_DEFINITION_NAME)) { + for (auto &x : grammar) { + auto &rule = x.second; + auto ope = rule.get_core_operator(); + if (IsLiteralToken::check(*ope)) { rule <= tok(ope); } + } + + auto &rule = grammar[WHITESPACE_DEFINITION_NAME]; + start_rule.whitespaceOpe = wsp(rule.get_core_operator()); + + if (detect_infiniteLoop(data, rule, log, s)) { return {}; } + } + + // Word expression + if (grammar.count(WORD_DEFINITION_NAME)) { + auto &rule = grammar[WORD_DEFINITION_NAME]; + start_rule.wordOpe = rule.get_core_operator(); + + if (detect_infiniteLoop(data, rule, log, s)) { return {}; } + } + + // Apply instructions + for (const auto &[name, instructions] : data.instructions) { + auto &rule = grammar[name]; + + for (const auto &instruction : instructions) { + if (instruction.type == "precedence") { + const auto &info = + std::any_cast(instruction.data); + + if (!apply_precedence_instruction(rule, info, s, log)) { return {}; } + } else if (instruction.type == "error_message") { + rule.error_message = std::any_cast(instruction.data); + } else if (instruction.type == "no_ast_opt") { + rule.no_ast_opt = true; + } + } + } + + // Setup First-Set and ISpan optimizations + for (auto &x : grammar) { + SetupFirstSets vis; + x.second.accept(vis); + } + + return {data.grammar, start, data.enablePackratParsing}; + } + + bool detect_infiniteLoop(const Data &data, Definition &rule, const Log &log, + const char *s) const { + std::vector> refs; + std::unordered_map has_error_cache; + DetectInfiniteLoop vis(data.start_pos, rule.name, refs, has_error_cache); + rule.accept(vis); + if (vis.has_error) { + if (log) { + auto line = line_info(s, vis.error_s); + log(line.first, line.second, + "infinite loop is detected in '" + vis.error_name + "'.", ""); + } + return true; + } + return false; + } + + Grammar g; }; /*----------------------------------------------------------------------------- @@ -4254,163 +5069,163 @@ private: *---------------------------------------------------------------------------*/ template struct AstBase : public Annotation { - AstBase(const char *path, size_t line, size_t column, const char *name, - const std::vector> &nodes, - size_t position = 0, size_t length = 0, size_t choice_count = 0, - size_t choice = 0) - : path(path ? path : ""), line(line), column(column), name(name), - position(position), length(length), choice_count(choice_count), - choice(choice), original_name(name), - original_choice_count(choice_count), original_choice(choice), - tag(str2tag(name)), original_tag(tag), is_token(false), nodes(nodes) {} + AstBase(const char *path, size_t line, size_t column, const char *name, + const std::vector> &nodes, + size_t position = 0, size_t length = 0, size_t choice_count = 0, + size_t choice = 0) + : path(path ? path : ""), line(line), column(column), name(name), + position(position), length(length), choice_count(choice_count), + choice(choice), original_name(name), + original_choice_count(choice_count), original_choice(choice), + tag(str2tag(name)), original_tag(tag), is_token(false), nodes(nodes) {} - AstBase(const char *path, size_t line, size_t column, const char *name, - const std::string_view &token, size_t position = 0, size_t length = 0, - size_t choice_count = 0, size_t choice = 0) - : path(path ? path : ""), line(line), column(column), name(name), - position(position), length(length), choice_count(choice_count), - choice(choice), original_name(name), - original_choice_count(choice_count), original_choice(choice), - tag(str2tag(name)), original_tag(tag), is_token(true), token(token) {} + AstBase(const char *path, size_t line, size_t column, const char *name, + const std::string_view &token, size_t position = 0, size_t length = 0, + size_t choice_count = 0, size_t choice = 0) + : path(path ? path : ""), line(line), column(column), name(name), + position(position), length(length), choice_count(choice_count), + choice(choice), original_name(name), + original_choice_count(choice_count), original_choice(choice), + tag(str2tag(name)), original_tag(tag), is_token(true), token(token) {} - AstBase(const AstBase &ast, const char *original_name, size_t position = 0, - size_t length = 0, size_t original_choice_count = 0, - size_t original_choice = 0) - : path(ast.path), line(ast.line), column(ast.column), name(ast.name), - position(position), length(length), choice_count(ast.choice_count), - choice(ast.choice), original_name(original_name), - original_choice_count(original_choice_count), - original_choice(original_choice), tag(ast.tag), - original_tag(str2tag(original_name)), is_token(ast.is_token), - token(ast.token), nodes(ast.nodes), parent(ast.parent) {} + AstBase(const AstBase &ast, const char *original_name, size_t position = 0, + size_t length = 0, size_t original_choice_count = 0, + size_t original_choice = 0) + : path(ast.path), line(ast.line), column(ast.column), name(ast.name), + position(position), length(length), choice_count(ast.choice_count), + choice(ast.choice), original_name(original_name), + original_choice_count(original_choice_count), + original_choice(original_choice), tag(ast.tag), + original_tag(str2tag(original_name)), is_token(ast.is_token), + token(ast.token), nodes(ast.nodes), parent(ast.parent) {} - const std::string path; - const size_t line = 1; - const size_t column = 1; + const std::string path; + const size_t line = 1; + const size_t column = 1; - const std::string name; - size_t position; - size_t length; - const size_t choice_count; - const size_t choice; - const std::string original_name; - const size_t original_choice_count; - const size_t original_choice; - const unsigned int tag; - const unsigned int original_tag; + const std::string name; + size_t position; + size_t length; + const size_t choice_count; + const size_t choice; + const std::string original_name; + const size_t original_choice_count; + const size_t original_choice; + const unsigned int tag; + const unsigned int original_tag; - const bool is_token; - const std::string_view token; + const bool is_token; + const std::string_view token; - std::vector>> nodes; - std::weak_ptr> parent; + std::vector>> nodes; + std::weak_ptr> parent; - std::string token_to_string() const { - assert(is_token); - return std::string(token); - } + std::string token_to_string() const { + assert(is_token); + return std::string(token); + } - template T token_to_number() const { - return token_to_number_(token); - } + template T token_to_number() const { + return token_to_number_(token); + } }; template void ast_to_s_core(const std::shared_ptr &ptr, std::string &s, int level, std::function fn) { - const auto &ast = *ptr; - for (auto i = 0; i < level; i++) { - s += " "; - } - auto name = ast.original_name; - if (ast.original_choice_count > 0) { - name += "/" + std::to_string(ast.original_choice); - } - if (ast.name != ast.original_name) { name += "[" + ast.name + "]"; } - if (ast.is_token) { - s += "- " + name + " ("; - s += ast.token; - s += ")\n"; - } else { - s += "+ " + name + "\n"; - } - if (fn) { s += fn(ast, level + 1); } - for (auto node : ast.nodes) { - ast_to_s_core(node, s, level + 1, fn); - } + const auto &ast = *ptr; + for (auto i = 0; i < level; i++) { + s += " "; + } + auto name = ast.original_name; + if (ast.original_choice_count > 0) { + name += "/" + std::to_string(ast.original_choice); + } + if (ast.name != ast.original_name) { name += "[" + ast.name + "]"; } + if (ast.is_token) { + s += "- " + name + " ("; + s += ast.token; + s += ")\n"; + } else { + s += "+ " + name + "\n"; + } + if (fn) { s += fn(ast, level + 1); } + for (const auto &node : ast.nodes) { + ast_to_s_core(node, s, level + 1, fn); + } } template std::string ast_to_s(const std::shared_ptr &ptr, std::function fn = nullptr) { - std::string s; - ast_to_s_core(ptr, s, 0, fn); - return s; + std::string s; + ast_to_s_core(ptr, s, 0, fn); + return s; } struct AstOptimizer { - AstOptimizer(bool mode, const std::vector &rules = {}) - : mode_(mode), rules_(rules) {} + AstOptimizer(bool mode, const std::vector &rules = {}) + : mode_(mode), rules_(rules) {} - template - std::shared_ptr optimize(std::shared_ptr original, - std::shared_ptr parent = nullptr) { - auto found = - std::find(rules_.begin(), rules_.end(), original->name) != rules_.end(); - auto opt = mode_ ? !found : found; + template + std::shared_ptr optimize(std::shared_ptr original, + std::shared_ptr parent = nullptr) { + auto found = + std::find(rules_.begin(), rules_.end(), original->name) != rules_.end(); + auto opt = mode_ ? !found : found; - if (opt && original->nodes.size() == 1) { - auto child = optimize(original->nodes[0], parent); - auto ast = std::make_shared(*child, original->name.data(), - original->choice_count, original->position, - original->length, original->choice); - for (auto node : ast->nodes) { - node->parent = ast; - } - return ast; - } - - auto ast = std::make_shared(*original); - ast->parent = parent; - ast->nodes.clear(); - for (auto node : original->nodes) { - auto child = optimize(node, ast); - ast->nodes.push_back(child); - } - return ast; + if (opt && original->nodes.size() == 1) { + auto child = optimize(original->nodes[0], parent); + auto ast = std::make_shared(*child, original->name.data(), + original->position, original->length, + original->choice_count, original->choice); + for (auto &node : ast->nodes) { + node->parent = ast; + } + return ast; } + auto ast = std::make_shared(*original); + ast->parent = parent; + ast->nodes.clear(); + for (const auto &node : original->nodes) { + auto child = optimize(node, ast); + ast->nodes.push_back(child); + } + return ast; + } + private: - const bool mode_; - const std::vector rules_; + const bool mode_; + const std::vector rules_; }; struct EmptyType {}; using Ast = AstBase; template void add_ast_action(Definition &rule) { - rule.action = [&](const SemanticValues &vs) { - auto line = vs.line_info(); + rule.action = [&](const SemanticValues &vs) { + auto line = vs.line_info(); - if (rule.is_token()) { - return std::make_shared( - vs.path, line.first, line.second, rule.name.data(), vs.token(), - std::distance(vs.ss, vs.sv().data()), vs.sv().length(), - vs.choice_count(), vs.choice()); - } + if (rule.is_token()) { + return std::make_shared( + vs.path, line.first, line.second, rule.name.data(), vs.token(), + std::distance(vs.ss, vs.sv().data()), vs.sv().length(), + vs.choice_count(), vs.choice()); + } - auto ast = - std::make_shared(vs.path, line.first, line.second, rule.name.data(), - vs.transform>(), - std::distance(vs.ss, vs.sv().data()), - vs.sv().length(), vs.choice_count(), vs.choice()); + auto ast = + std::make_shared(vs.path, line.first, line.second, rule.name.data(), + vs.transform>(), + std::distance(vs.ss, vs.sv().data()), + vs.sv().length(), vs.choice_count(), vs.choice()); - for (auto node : ast->nodes) { - node->parent = ast; - } - return ast; - }; + for (auto &node : ast->nodes) { + node->parent = ast; + } + return ast; + }; } #define PEG_EXPAND(...) __VA_ARGS__ @@ -4550,229 +5365,235 @@ template void add_ast_action(Definition &rule) { class parser { public: - parser() = default; + parser() = default; - parser(const char *s, size_t n, const Rules &rules, - std::string_view start = {}) { - load_grammar(s, n, rules, start); - } + parser(const char *s, size_t n, const Rules &rules, + std::string_view start = {}) { + load_grammar(s, n, rules, start); + } - parser(const char *s, size_t n, std::string_view start = {}) - : parser(s, n, Rules(), start) {} + parser(const char *s, size_t n, std::string_view start = {}) + : parser(s, n, Rules(), start) {} - parser(std::string_view sv, const Rules &rules, std::string_view start = {}) - : parser(sv.data(), sv.size(), rules, start) {} + parser(std::string_view sv, const Rules &rules, std::string_view start = {}) + : parser(sv.data(), sv.size(), rules, start) {} - parser(std::string_view sv, std::string_view start = {}) - : parser(sv.data(), sv.size(), Rules(), start) {} + parser(std::string_view sv, std::string_view start = {}) + : parser(sv.data(), sv.size(), Rules(), start) {} #if defined(__cpp_lib_char8_t) - parser(std::u8string_view sv, const Rules &rules, std::string_view start = {}) - : parser(reinterpret_cast(sv.data()), sv.size(), rules, - start) {} + parser(std::u8string_view sv, const Rules &rules, std::string_view start = {}) + : parser(reinterpret_cast(sv.data()), sv.size(), rules, + start) {} - parser(std::u8string_view sv, std::string_view start = {}) - : parser(reinterpret_cast(sv.data()), sv.size(), Rules(), - start) {} + parser(std::u8string_view sv, std::string_view start = {}) + : parser(reinterpret_cast(sv.data()), sv.size(), Rules(), + start) {} #endif - operator bool() { return grammar_ != nullptr; } + operator bool() const { return grammar_ != nullptr; } - bool load_grammar(const char *s, size_t n, const Rules &rules, - std::string_view start = {}) { - auto cxt = ParserGenerator::parse(s, n, rules, log_, start); - grammar_ = cxt.grammar; - start_ = cxt.start; - enablePackratParsing_ = cxt.enablePackratParsing; - return grammar_ != nullptr; + bool load_grammar(const char *s, size_t n, const Rules &rules, + std::string_view start = {}) { + auto cxt = + ParserGenerator::parse(s, n, rules, log_, start, enableLeftRecursion_); + grammar_ = cxt.grammar; + start_ = cxt.start; + enablePackratParsing_ = cxt.enablePackratParsing; + return grammar_ != nullptr; + } + + bool load_grammar(const char *s, size_t n, std::string_view start = {}) { + return load_grammar(s, n, Rules(), start); + } + + bool load_grammar(std::string_view sv, const Rules &rules, + std::string_view start = {}) { + return load_grammar(sv.data(), sv.size(), rules, start); + } + + bool load_grammar(std::string_view sv, std::string_view start = {}) { + return load_grammar(sv.data(), sv.size(), Rules(), start); + } + + bool parse_n(const char *s, size_t n, const char *path = nullptr) const { + if (grammar_ != nullptr) { + const auto &rule = (*grammar_)[start_]; + auto result = rule.parse(s, n, path, log_); + return post_process(s, n, result); } + return false; + } - bool load_grammar(const char *s, size_t n, std::string_view start = {}) { - return load_grammar(s, n, Rules(), start); - } - - bool load_grammar(std::string_view sv, const Rules &rules, - std::string_view start = {}) { - return load_grammar(sv.data(), sv.size(), rules, start); - } - - bool load_grammar(std::string_view sv, std::string_view start = {}) { - return load_grammar(sv.data(), sv.size(), start); - } - - bool parse_n(const char *s, size_t n, const char *path = nullptr) const { - if (grammar_ != nullptr) { - const auto &rule = (*grammar_)[start_]; - auto result = rule.parse(s, n, path, log_); - return post_process(s, n, result); - } - return false; - } - - bool parse_n(const char *s, size_t n, std::any &dt, - const char *path = nullptr) const { - if (grammar_ != nullptr) { - const auto &rule = (*grammar_)[start_]; - auto result = rule.parse(s, n, dt, path, log_); - return post_process(s, n, result); - } - return false; - } - - template - bool parse_n(const char *s, size_t n, T &val, - const char *path = nullptr) const { - if (grammar_ != nullptr) { - const auto &rule = (*grammar_)[start_]; - auto result = rule.parse_and_get_value(s, n, val, path, log_); - return post_process(s, n, result); - } - return false; - } - - template - bool parse_n(const char *s, size_t n, std::any &dt, T &val, - const char *path = nullptr) const { - if (grammar_ != nullptr) { - const auto &rule = (*grammar_)[start_]; - auto result = rule.parse_and_get_value(s, n, dt, val, path, log_); - return post_process(s, n, result); - } - return false; - } - - bool parse(std::string_view sv, const char *path = nullptr) const { - return parse_n(sv.data(), sv.size(), path); - } - - bool parse(std::string_view sv, std::any &dt, + bool parse_n(const char *s, size_t n, std::any &dt, const char *path = nullptr) const { - return parse_n(sv.data(), sv.size(), dt, path); + if (grammar_ != nullptr) { + const auto &rule = (*grammar_)[start_]; + auto result = rule.parse(s, n, dt, path, log_); + return post_process(s, n, result); } + return false; + } - template - bool parse(std::string_view sv, T &val, const char *path = nullptr) const { - return parse_n(sv.data(), sv.size(), val, path); - } - - template - bool parse(std::string_view sv, std::any &dt, T &val, + template + bool parse_n(const char *s, size_t n, T &val, const char *path = nullptr) const { - return parse_n(sv.data(), sv.size(), dt, val, path); + if (grammar_ != nullptr) { + const auto &rule = (*grammar_)[start_]; + auto result = rule.parse_and_get_value(s, n, val, path, log_); + return post_process(s, n, result); } + return false; + } + + template + bool parse_n(const char *s, size_t n, std::any &dt, T &val, + const char *path = nullptr) const { + if (grammar_ != nullptr) { + const auto &rule = (*grammar_)[start_]; + auto result = rule.parse_and_get_value(s, n, dt, val, path, log_); + return post_process(s, n, result); + } + return false; + } + + bool parse(std::string_view sv, const char *path = nullptr) const { + return parse_n(sv.data(), sv.size(), path); + } + + bool parse(std::string_view sv, std::any &dt, + const char *path = nullptr) const { + return parse_n(sv.data(), sv.size(), dt, path); + } + + template + bool parse(std::string_view sv, T &val, const char *path = nullptr) const { + return parse_n(sv.data(), sv.size(), val, path); + } + + template + bool parse(std::string_view sv, std::any &dt, T &val, + const char *path = nullptr) const { + return parse_n(sv.data(), sv.size(), dt, val, path); + } #if defined(__cpp_lib_char8_t) - bool parse(std::u8string_view sv, const char *path = nullptr) const { - return parse_n(reinterpret_cast(sv.data()), sv.size(), path); - } + bool parse(std::u8string_view sv, const char *path = nullptr) const { + return parse_n(reinterpret_cast(sv.data()), sv.size(), path); + } - bool parse(std::u8string_view sv, std::any &dt, - const char *path = nullptr) const { - return parse_n(reinterpret_cast(sv.data()), sv.size(), dt, - path); - } + bool parse(std::u8string_view sv, std::any &dt, + const char *path = nullptr) const { + return parse_n(reinterpret_cast(sv.data()), sv.size(), dt, + path); + } - template - bool parse(std::u8string_view sv, T &val, const char *path = nullptr) const { - return parse_n(reinterpret_cast(sv.data()), sv.size(), val, - path); - } + template + bool parse(std::u8string_view sv, T &val, const char *path = nullptr) const { + return parse_n(reinterpret_cast(sv.data()), sv.size(), val, + path); + } - template - bool parse(std::u8string_view sv, std::any &dt, T &val, - const char *path = nullptr) const { - return parse_n(reinterpret_cast(sv.data()), sv.size(), dt, - val, path); - } + template + bool parse(std::u8string_view sv, std::any &dt, T &val, + const char *path = nullptr) const { + return parse_n(reinterpret_cast(sv.data()), sv.size(), dt, + val, path); + } #endif - Definition &operator[](const char *s) { return (*grammar_)[s]; } + Definition &operator[](const char *s) { return (*grammar_)[s]; } - const Definition &operator[](const char *s) const { return (*grammar_)[s]; } + const Definition &operator[](const char *s) const { return (*grammar_)[s]; } - const Grammar &get_grammar() const { return *grammar_; } + const Grammar &get_grammar() const { return *grammar_; } - void disable_eoi_check() { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.eoi_check = false; - } + void disable_eoi_check() { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.eoi_check = false; } + } - void enable_packrat_parsing() { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.enablePackratParsing = enablePackratParsing_; - } + void enable_left_recursion(bool enable = true) { + enableLeftRecursion_ = enable; + } + + void enable_packrat_parsing() { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.enablePackratParsing = enablePackratParsing_; } + } - void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave) { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.tracer_enter = tracer_enter; - rule.tracer_leave = tracer_leave; - } + void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave) { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.tracer_enter = tracer_enter; + rule.tracer_leave = tracer_leave; } + } - void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave, - TracerStartOrEnd tracer_start, - TracerStartOrEnd tracer_end) { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.tracer_enter = tracer_enter; - rule.tracer_leave = tracer_leave; - rule.tracer_start = tracer_start; - rule.tracer_end = tracer_end; - } + void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave, + TracerStartOrEnd tracer_start, + TracerStartOrEnd tracer_end) { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.tracer_enter = tracer_enter; + rule.tracer_leave = tracer_leave; + rule.tracer_start = tracer_start; + rule.tracer_end = tracer_end; } + } - void set_verbose_trace(bool verbose_trace) { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.verbose_trace = verbose_trace; - } + void set_verbose_trace(bool verbose_trace) { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.verbose_trace = verbose_trace; } + } - template parser &enable_ast() { - for (auto &[_, rule] : *grammar_) { - if (!rule.action) { add_ast_action(rule); } - } - return *this; + template parser &enable_ast() { + for (auto &[_, rule] : *grammar_) { + if (!rule.action) { add_ast_action(rule); } } + return *this; + } - template - std::shared_ptr optimize_ast(std::shared_ptr ast, - bool opt_mode = true) const { - return AstOptimizer(opt_mode, get_no_ast_opt_rules()).optimize(ast); - } + template + std::shared_ptr optimize_ast(std::shared_ptr ast, + bool opt_mode = true) const { + return AstOptimizer(opt_mode, get_no_ast_opt_rules()).optimize(ast); + } - void set_logger(Log log) { log_ = log; } + void set_logger(Log log) { log_ = log; } - void set_logger( - std::function - log) { - log_ = [log](size_t line, size_t col, const std::string &msg, - const std::string & /*rule*/) { log(line, col, msg); }; - } + void set_logger( + std::function + log) { + log_ = [log](size_t line, size_t col, const std::string &msg, + const std::string & /*rule*/) { log(line, col, msg); }; + } private: - bool post_process(const char *s, size_t n, Definition::Result &r) const { - if (log_ && !r.ret) { r.error_info.output_log(log_, s, n); } - return r.ret && !r.recovered; - } + bool post_process(const char *s, size_t n, Definition::Result &r) const { + if (log_ && !r.ret) { r.error_info.output_log(log_, s, n); } + return r.ret && !r.recovered; + } - std::vector get_no_ast_opt_rules() const { - std::vector rules; - for (auto &[name, rule] : *grammar_) { - if (rule.no_ast_opt) { rules.push_back(name); } - } - return rules; + std::vector get_no_ast_opt_rules() const { + std::vector rules; + for (auto &[name, rule] : *grammar_) { + if (rule.no_ast_opt) { rules.push_back(name); } } + return rules; + } - std::shared_ptr grammar_; - std::string start_; - bool enablePackratParsing_ = false; - Log log_; + std::shared_ptr grammar_; + std::string start_; + bool enableLeftRecursion_ = true; + bool enablePackratParsing_ = false; + Log log_; }; /*----------------------------------------------------------------------------- @@ -4780,59 +5601,59 @@ private: *---------------------------------------------------------------------------*/ inline void enable_tracing(parser &parser, std::ostream &os) { - parser.enable_trace( - [&](auto &ope, auto s, auto, auto &, auto &c, auto &, auto &trace_data) { - auto prev_pos = std::any_cast(trace_data); - auto pos = static_cast(s - c.s); - auto backtrack = (pos < prev_pos ? "*" : ""); - std::string indent; - auto level = c.trace_ids.size() - 1; - while (level--) { - indent += "│"; - } - std::string name; - { - name = peg::TraceOpeName::get(const_cast(ope)); + parser.enable_trace( + [&](auto &ope, auto s, auto, auto &, auto &c, auto &, auto &trace_data) { + auto prev_pos = std::any_cast(trace_data); + auto pos = static_cast(s - c.s); + auto backtrack = (pos < prev_pos ? "*" : ""); + std::string indent; + auto level = c.trace_ids.size() - 1; + while (level--) { + indent += "│"; + } + std::string name; + { + name = peg::TraceOpeName::get(const_cast(ope)); - auto lit = dynamic_cast(&ope); - if (lit) { name += " '" + peg::escape_characters(lit->lit_) + "'"; } - } - os << "E " << pos + 1 << backtrack << "\t" << indent << "┌" << name - << " #" << c.trace_ids.back() << std::endl; - trace_data = static_cast(pos); - }, - [&](auto &ope, auto s, auto, auto &sv, auto &c, auto &, auto len, - auto &) { - auto pos = static_cast(s - c.s); - if (len != static_cast(-1)) { pos += len; } - std::string indent; - auto level = c.trace_ids.size() - 1; - while (level--) { - indent += "│"; - } - auto ret = len != static_cast(-1) ? "└o " : "└x "; - auto name = peg::TraceOpeName::get(const_cast(ope)); - std::stringstream choice; - if (sv.choice_count() > 0) { - choice << " " << sv.choice() << "/" << sv.choice_count(); - } - std::string token; - if (!sv.tokens.empty()) { - token += ", token '"; - token += sv.tokens[0]; - token += "'"; - } - std::string matched; - if (peg::success(len) && - peg::TokenChecker::is_token(const_cast(ope))) { - matched = ", match '" + peg::escape_characters(s, len) + "'"; - } - os << "L " << pos + 1 << "\t" << indent << ret << name << " #" - << c.trace_ids.back() << choice.str() << token << matched - << std::endl; - }, - [&](auto &trace_data) { trace_data = static_cast(0); }, - [&](auto &) {}); + auto lit = dynamic_cast(&ope); + if (lit) { name += " '" + peg::escape_characters(lit->lit_) + "'"; } + } + os << "E " << pos + 1 << backtrack << "\t" << indent << "┌" << name + << " #" << c.trace_ids.back() << std::endl; + trace_data = static_cast(pos); + }, + [&](auto &ope, auto s, auto, auto &sv, auto &c, auto &, auto len, + auto &) { + auto pos = static_cast(s - c.s); + if (len != static_cast(-1)) { pos += len; } + std::string indent; + auto level = c.trace_ids.size() - 1; + while (level--) { + indent += "│"; + } + auto ret = len != static_cast(-1) ? "└o " : "└x "; + auto name = peg::TraceOpeName::get(const_cast(ope)); + std::stringstream choice; + if (sv.choice_count() > 0) { + choice << " " << sv.choice() << "/" << sv.choice_count(); + } + std::string token; + if (!sv.tokens.empty()) { + token += ", token '"; + token += sv.tokens[0]; + token += "'"; + } + std::string matched; + if (peg::success(len) && + peg::TokenChecker::is_token(const_cast(ope))) { + matched = ", match '" + peg::escape_characters(s, len) + "'"; + } + os << "L " << pos + 1 << "\t" << indent << ret << name << " #" + << c.trace_ids.back() << choice.str() << token << matched + << std::endl; + }, + [&](auto &trace_data) { trace_data = static_cast(0); }, + [&](auto &) {}); } /*----------------------------------------------------------------------------- @@ -4840,98 +5661,98 @@ inline void enable_tracing(parser &parser, std::ostream &os) { *---------------------------------------------------------------------------*/ inline void enable_profiling(parser &parser, std::ostream &os) { - struct Stats { - struct Item { - std::string name; - size_t success; - size_t fail; - }; - std::vector items; - std::map index; - size_t total = 0; - std::chrono::steady_clock::time_point start; + struct Stats { + struct Item { + std::string name; + size_t success; + size_t fail; }; + std::vector items; + std::map index; + size_t total = 0; + std::chrono::steady_clock::time_point start; + }; - parser.enable_trace( - [&](auto &ope, auto, auto, auto &, auto &, auto &, std::any &trace_data) { - if (auto holder = dynamic_cast(&ope)) { - auto &stats = *std::any_cast(trace_data); + parser.enable_trace( + [&](auto &ope, auto, auto, auto &, auto &, auto &, std::any &trace_data) { + if (auto holder = dynamic_cast(&ope)) { + auto &stats = *std::any_cast(trace_data); - auto &name = holder->name(); - if (stats.index.find(name) == stats.index.end()) { - stats.index[name] = stats.index.size(); - stats.items.push_back({name, 0, 0}); - } - stats.total++; + auto &name = holder->name(); + if (stats.index.find(name) == stats.index.end()) { + stats.index[name] = stats.index.size(); + stats.items.push_back({name, 0, 0}); + } + stats.total++; + } + }, + [&](auto &ope, auto, auto, auto &, auto &, auto &, auto len, + std::any &trace_data) { + if (auto holder = dynamic_cast(&ope)) { + auto &stats = *std::any_cast(trace_data); + + auto &name = holder->name(); + auto index = stats.index[name]; + auto &stat = stats.items[index]; + if (len != static_cast(-1)) { + stat.success++; + } else { + stat.fail++; + } + + if (index == 0) { + auto end = std::chrono::steady_clock::now(); + auto nano = std::chrono::duration_cast( + end - stats.start) + .count(); + auto sec = nano / 1000000.0; + os << "duration: " << sec << "s (" << nano << "µs)" << std::endl + << std::endl; + + char buff[BUFSIZ]; + size_t total_success = 0; + size_t total_fail = 0; + for (auto &[name, success, fail] : stats.items) { + total_success += success; + total_fail += fail; } - }, - [&](auto &ope, auto, auto, auto &, auto &, auto &, auto len, - std::any &trace_data) { - if (auto holder = dynamic_cast(&ope)) { - auto &stats = *std::any_cast(trace_data); - auto &name = holder->name(); - auto index = stats.index[name]; - auto &stat = stats.items[index]; - if (len != static_cast(-1)) { - stat.success++; - } else { - stat.fail++; - } + os << " id total % success fail " + "definition" + << std::endl; - if (index == 0) { - auto end = std::chrono::steady_clock::now(); - auto nano = std::chrono::duration_cast( - end - stats.start) - .count(); - auto sec = nano / 1000000.0; - os << "duration: " << sec << "s (" << nano << "µs)" << std::endl - << std::endl; + auto grand_total = total_success + total_fail; + snprintf(buff, BUFSIZ, "%4s %10zu %5s %10zu %10zu %s", "", + grand_total, "", total_success, total_fail, + "Total counters"); + os << buff << std::endl; - char buff[BUFSIZ]; - size_t total_success = 0; - size_t total_fail = 0; - for (auto &[name_, success, fail] : stats.items) { - total_success += success; - total_fail += fail; - } + snprintf(buff, BUFSIZ, "%4s %10s %5s %10.2f %10.2f %s", "", "", + "", total_success * 100.0 / grand_total, + total_fail * 100.0 / grand_total, "% success/fail"); + os << buff << std::endl << std::endl; + ; - os << " id total % success fail " - "definition" - << std::endl; - - auto grand_total = total_success + total_fail; - snprintf(buff, BUFSIZ, "%4s %10zu %5s %10zu %10zu %s", "", - grand_total, "", total_success, total_fail, - "Total counters"); - os << buff << std::endl; - - snprintf(buff, BUFSIZ, "%4s %10s %5s %10.2f %10.2f %s", "", "", - "", total_success * 100.0 / grand_total, - total_fail * 100.0 / grand_total, "% success/fail"); - os << buff << std::endl << std::endl; - ; - - size_t id = 0; - for (auto &[name_, success, fail] : stats.items) { - auto total = success + fail; - auto ratio = total * 100.0 / stats.total; - snprintf(buff, BUFSIZ, "%4zu %10zu %5.2f %10zu %10zu %s", id, - total, ratio, success, fail, name.c_str()); - os << buff << std::endl; - id++; - } - } + size_t id = 0; + for (auto &[name, success, fail] : stats.items) { + auto total = success + fail; + auto ratio = total * 100.0 / stats.total; + snprintf(buff, BUFSIZ, "%4zu %10zu %5.2f %10zu %10zu %s", id, + total, ratio, success, fail, name.c_str()); + os << buff << std::endl; + id++; } - }, - [&](auto &trace_data) { - auto stats = new Stats{}; - stats->start = std::chrono::steady_clock::now(); - trace_data = stats; - }, - [&](auto &trace_data) { - auto stats = std::any_cast(trace_data); - delete stats; - }); + } + } + }, + [&](auto &trace_data) { + auto stats = new Stats{}; + stats->start = std::chrono::steady_clock::now(); + trace_data = stats; + }, + [&](auto &trace_data) { + auto stats = std::any_cast(trace_data); + delete stats; + }); } } // namespace peg From f3370a4f52b3a419ac47ea0b848fbb0d3cbf05e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 07:05:24 +0100 Subject: [PATCH 20/60] Bump doc/doxygen/theme from `1f36200` to `d52eafe` (#6751) Bumps [doc/doxygen/theme](https://github.com/jothepro/doxygen-awesome-css) from `1f36200` to `d52eafe`. - [Release notes](https://github.com/jothepro/doxygen-awesome-css/releases) - [Commits](https://github.com/jothepro/doxygen-awesome-css/compare/1f3620084ff75734ed192101acf40e9dff01d848...d52eafe3e9303399fda15661f3d7bb8fe3d7eabc) --- updated-dependencies: - dependency-name: doc/doxygen/theme dependency-version: d52eafe3e9303399fda15661f3d7bb8fe3d7eabc dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- doc/doxygen/theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doxygen/theme b/doc/doxygen/theme index 1f3620084..d52eafe3e 160000 --- a/doc/doxygen/theme +++ b/doc/doxygen/theme @@ -1 +1 @@ -Subproject commit 1f3620084ff75734ed192101acf40e9dff01d848 +Subproject commit d52eafe3e9303399fda15661f3d7bb8fe3d7eabc From 9dfac77ba28ad9a91fb5df683fd671e7e9c6ee08 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:49:17 +0200 Subject: [PATCH 21/60] Update translation source strings (#6762) --- cockatrice/cockatrice_en@source.ts | 2447 ++++++++++++++++------------ oracle/oracle_en@source.ts | 4 +- 2 files changed, 1362 insertions(+), 1089 deletions(-) diff --git a/cockatrice/cockatrice_en@source.ts b/cockatrice/cockatrice_en@source.ts index 9a2f56f07..810b345df 100644 --- a/cockatrice/cockatrice_en@source.ts +++ b/cockatrice/cockatrice_en@source.ts @@ -25,12 +25,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -38,60 +38,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -133,190 +133,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings - + Current theme: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering - + Display card names on cards having a picture - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout - + Display hand horizontally (wastes space) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -324,7 +302,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -643,22 +626,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -694,124 +677,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -827,133 +810,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1015,7 +998,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1023,7 +1006,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info @@ -1046,32 +1029,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1079,32 +1062,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1177,17 +1160,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1310,7 +1293,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1318,166 +1301,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1485,32 +1468,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1518,27 +1501,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count - + Set - + Number - + Provider ID - + Card @@ -1546,12 +1529,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1648,94 +1631,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1768,32 +1751,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1860,29 +1843,29 @@ This is only saved for moderators and cannot be seen by the banned person. - - + + Error - + The selected file could not be loaded. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2884,17 +2867,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard - + Error - + Invalid deck list. @@ -2902,43 +2885,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2957,40 +2940,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3151,12 +3167,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3167,7 +3183,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -3178,7 +3194,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3187,21 +3203,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3210,59 +3226,59 @@ Would you like to change your database location setting? - - - + + + Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Appearance - + User Interface - + Card Sources - + Chat - + Sound - + Shortcuts @@ -3575,67 +3591,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4089,143 +4105,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path - + Personal settings - + Language: - + Paths (editing disabled in portable mode) - + Paths - + How to help with translations - + Decks directory: - + Filters directory: - + Replays directory: - + Pictures directory: - + Card database: - + Custom database directory: - + Token database: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4233,47 +4249,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4281,88 +4297,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4370,52 +4386,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4423,193 +4439,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4703,18 +4739,8 @@ Will now login. - - Number of players - - - - - Please enter the number of players. - - - - - + + Player %1 @@ -4817,8 +4843,8 @@ Will now login. - - + + Error @@ -5219,63 +5245,63 @@ Local version is %1, remote version is %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5285,81 +5311,81 @@ Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5367,54 +5393,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -5465,7 +5491,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5619,277 +5645,297 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard - + from the stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). @@ -5897,12 +5943,12 @@ Cockatrice will now reload the card database. - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural @@ -5911,72 +5957,72 @@ Cockatrice will now reload the card database. - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). @@ -5984,28 +6030,28 @@ Cockatrice will now reload the card database. - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural @@ -6014,107 +6060,107 @@ Cockatrice will now reload the card database. - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). @@ -6122,7 +6168,7 @@ Cockatrice will now reload the card database. - + %1 removes %2 "%3" counter(s) from %4 (now %5). @@ -6130,97 +6176,97 @@ Cockatrice will now reload the card database. - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6228,110 +6274,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6344,32 +6390,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6511,57 +6562,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -6578,134 +6629,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6713,48 +6768,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6803,17 +6875,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6952,6 +7032,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7100,37 +7207,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7173,6 +7280,14 @@ Cockatrice will now reload the card database. + + SayMenu + + + S&ay + + + SequenceEdit @@ -7237,53 +7352,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7350,27 +7465,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7586,59 +7701,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7646,60 +7825,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - - - - - + Visible - - - - - + Floating - + Reset layout - + Deck: %1 @@ -7707,61 +7878,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7826,7 +7991,7 @@ Please check your shortcut settings! - + New folder @@ -7907,18 +8072,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: @@ -7936,12 +8101,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -7990,197 +8155,191 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - - - + Visible - - - - + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -9280,142 +9439,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - - - - - &Tap/untap animation - - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod + Animation settings + + + + + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9479,23 +9648,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9522,25 +9692,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9548,22 +9801,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9599,7 +9862,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9607,19 +9870,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9627,27 +9890,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9661,52 +9934,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9714,56 +9957,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9771,17 +10022,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9797,47 +10048,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9938,133 +10194,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10072,72 +10328,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view @@ -10172,7 +10428,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -10253,7 +10509,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10405,7 +10661,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout @@ -10826,7 +11082,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10846,98 +11103,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players - - - - Exile + + Bottom of Library + + + + Exile + + + + - + Graveyard - + Hand - - + + Top of Library - - + Battlefield, Face Down @@ -10973,234 +11234,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/oracle/oracle_en@source.ts b/oracle/oracle_en@source.ts index 53ad27b4d..943b44a97 100644 --- a/oracle/oracle_en@source.ts +++ b/oracle/oracle_en@source.ts @@ -278,7 +278,7 @@ OracleImporter - + Dummy set containing tokens @@ -286,7 +286,7 @@ OracleWizard - + Oracle Importer From ba786a0289a8dbfcaf833d1452c018ff1575956a Mon Sep 17 00:00:00 2001 From: tooomm Date: Wed, 1 Apr 2026 14:54:27 +0200 Subject: [PATCH 22/60] Update dependabot.yml (#6756) --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fc25af67f..88bed3663 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,9 @@ updates: - package-ecosystem: "gitsubmodule" # Look for `.gitmodules` in the `root` directory directory: "/" + ignore: + # Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used) & macOS Intel triplet broken with newer releases) + - dependency-name: "vcpkg" # Check for updates once a month schedule: interval: "monthly" From 690a00aa6c0779a8ffe67323bf21f6c793689920 Mon Sep 17 00:00:00 2001 From: SlightlyCircuitous <71394296+SlightlyCircuitous@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:40:42 -0400 Subject: [PATCH 23/60] Add Ubuntu 26.04 "Resolute Racoon" build (#6766) * Create 26.04 Dockerfile * Update desktop-build.yml * Update release_template.md * Add ca-certificates package to build --- .ci/Ubuntu26.04/Dockerfile | 29 +++++++++++++++++++++++++++++ .ci/release_template.md | 1 + .github/workflows/desktop-build.yml | 5 +++++ 3 files changed, 35 insertions(+) create mode 100644 .ci/Ubuntu26.04/Dockerfile diff --git a/.ci/Ubuntu26.04/Dockerfile b/.ci/Ubuntu26.04/Dockerfile new file mode 100644 index 000000000..7b0cd389f --- /dev/null +++ b/.ci/Ubuntu26.04/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:26.04 + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + ccache \ + clang-format \ + cmake \ + file \ + g++ \ + git \ + libgl-dev \ + liblzma-dev \ + libmariadb-dev-compat \ + libprotobuf-dev \ + libqt6multimedia6 \ + libqt6sql6-mysql \ + ninja-build \ + protobuf-compiler \ + qt6-image-formats-plugins \ + qt6-l10n-tools \ + qt6-multimedia-dev \ + qt6-svg-dev \ + qt6-tools-dev \ + qt6-tools-dev-tools \ + qt6-websockets-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* diff --git a/.ci/release_template.md b/.ci/release_template.md index b0924b92a..1ce9f4bf7 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -17,6 +17,7 @@ Available pre-compiled binaries for installation: • macOS 13+ Ventura Intel Linux + • Ubuntu 26.04 LTS Resolute RacoonUbuntu 24.04 LTS Noble NumbatUbuntu 22.04 LTS Jammy JellyfishDebian 13 Trixie diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 820044059..7bd84eb21 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -142,11 +142,16 @@ jobs: - distro: Ubuntu version: 22.04 package: DEB + test: skip # Running tests on all distros is superfluous - distro: Ubuntu version: 24.04 package: DEB + - distro: Ubuntu + version: 26.04 + package: DEB + name: ${{matrix.distro}} ${{matrix.version}} needs: configure runs-on: ubuntu-latest From a46ab5cd68dbbead94379778a0b6dca3dfaa89dc Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 4 Apr 2026 02:40:38 -0700 Subject: [PATCH 24/60] [Game] Allow uppercase X in expressions (#6767) --- libcockatrice_utility/libcockatrice/utility/expression.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcockatrice_utility/libcockatrice/utility/expression.cpp b/libcockatrice_utility/libcockatrice/utility/expression.cpp index 3b2e815d1..42073670c 100644 --- a/libcockatrice_utility/libcockatrice/utility/expression.cpp +++ b/libcockatrice_utility/libcockatrice/utility/expression.cpp @@ -20,7 +20,7 @@ peg::parser math(R"( NUMBER <- < '-'? [0-9]+ > NAME <- < [a-z][a-z0-9]* > - VARIABLE <- < [x] > + VARIABLE <- < [xX] > FUNCTION <- NAME '(' EXPRESSION ( [,\n] EXPRESSION )* ')' %whitespace <- [ \t\r]* From 3ec9ae97723252b3d4d7d821968d775a739893af Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sun, 5 Apr 2026 21:52:46 +0200 Subject: [PATCH 25/60] fix compiling on arch (#6768) * fix compiling on arch * redo all the logging in affected files --- .../interface/widgets/tabs/tab_replays.cpp | 17 ++- cockatrice/src/interface/window_main.cpp | 9 +- .../network/client/remote/remote_client.cpp | 46 +++--- .../protocol/debug_pb_message.cpp | 8 +- servatrice/src/isl_interface.cpp | 51 ++++--- .../src/servatrice_database_interface.cpp | 105 +++++++------- servatrice/src/serversocketinterface.cpp | 131 +++++++++++------- 7 files changed, 219 insertions(+), 148 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp index 2db9e83c5..570677ada 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp @@ -30,6 +30,8 @@ #include #include +inline Q_LOGGING_CATEGORY(TabReplaysLog, "replays_tab"); + TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo) : Tab(_tabSupervisor), client(_client) { @@ -265,9 +267,11 @@ void TabReplays::actOpenLocalReplay() f.close(); GameReplay *replay = new GameReplay; - replay->ParseFromArray(_data.data(), _data.size()); - - emit openReplay(replay); + if (replay->ParseFromArray(_data.data(), _data.size())) { + emit openReplay(replay); + } else { + qCWarning(TabReplaysLog) << "could not parse replay!"; + } } } @@ -379,9 +383,12 @@ void TabReplays::openRemoteReplayFinished(const Response &r) const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext); GameReplay *replay = new GameReplay; - replay->ParseFromString(resp.replay_data()); + if (replay->ParseFromString(resp.replay_data())) { - emit openReplay(replay); + emit openReplay(replay); + } else { + qCWarning(TabReplaysLog) << "could not parse remote replay!"; + } } void TabReplays::actDownload() diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index fbba6c3f5..86e6c1534 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -84,6 +84,7 @@ const QString MainWindow::appName = "Cockatrice"; const QStringList MainWindow::fileNameFilters = QStringList() << QObject::tr("Cockatrice card database (*.xml)") << QObject::tr("All files (*.*)"); +inline Q_LOGGING_CATEGORY(MainWindowLog, "main_window"); /** * Replaces the tab-specific menus that are shown in the menuBar. @@ -277,9 +278,11 @@ void MainWindow::actWatchReplay() file.close(); replay = new GameReplay; - replay->ParseFromArray(buf.data(), buf.size()); - - tabSupervisor->openReplay(replay); + if (replay->ParseFromArray(buf.data(), buf.size())) { + tabSupervisor->openReplay(replay); + } else { + qCWarning(MainWindowLog) << "failed to parse replay!"; + } } void MainWindow::localGameEnded() diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index 4b5d3f1b8..3e3ec889d 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -390,14 +390,18 @@ void RemoteClient::readData() return; ServerMessage newServerMessage; - newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); - - qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + bool ok = newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); inputBuffer.remove(0, messageLength); messageInProgress = false; - processProtocolItem(newServerMessage); + if (ok) { + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + + processProtocolItem(newServerMessage); + } else { + qCDebug(RemoteClientLog) << "parsing error!"; + } if (getStatus() == StatusDisconnecting) // use thread-safe getter doDisconnectFromServer(); @@ -408,11 +412,13 @@ void RemoteClient::websocketMessageReceived(const QByteArray &message) { lastDataReceived = timeRunning; ServerMessage newServerMessage; - newServerMessage.ParseFromArray(message.data(), message.length()); + if (newServerMessage.ParseFromArray(message.data(), message.length())) { + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); - qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); - - processProtocolItem(newServerMessage); + processProtocolItem(newServerMessage); + } else { + qCDebug(RemoteClientLog) << "parsing error!"; + } } void RemoteClient::sendCommandContainer(const CommandContainer &cont) @@ -426,19 +432,27 @@ void RemoteClient::sendCommandContainer(const CommandContainer &cont) qCDebug(RemoteClientLog).noquote() << "OUT" << getSafeDebugString(cont); QByteArray buf; + bool ok; if (usingWebSocket) { buf.resize(size); - cont.SerializeToArray(buf.data(), size); - websocket->sendBinaryMessage(buf); + ok = cont.SerializeToArray(buf.data(), size); + if (ok) { + websocket->sendBinaryMessage(buf); + } } else { buf.resize(size + 4); - cont.SerializeToArray(buf.data() + 4, size); - buf.data()[3] = (unsigned char)size; - buf.data()[2] = (unsigned char)(size >> 8); - buf.data()[1] = (unsigned char)(size >> 16); - buf.data()[0] = (unsigned char)(size >> 24); + ok = cont.SerializeToArray(buf.data() + 4, size); + if (ok) { + buf.data()[3] = (unsigned char)size; + buf.data()[2] = (unsigned char)(size >> 8); + buf.data()[1] = (unsigned char)(size >> 16); + buf.data()[0] = (unsigned char)(size >> 24); - socket->write(buf); + socket->write(buf); + } + } + if (!ok) { + qCDebug(RemoteClientLog) << "transmit error!"; } } diff --git a/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp b/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp index f0c3448b5..718487c18 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp +++ b/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp @@ -101,6 +101,10 @@ QString getSafeDebugString(const ::google::protobuf::Message &message) #endif // GOOGLE_PROTOBUF_VERSION > 3004000 std::string debug_string; - printer.PrintToString(message, &debug_string); - return QString::number(size) + " bytes " + QString::fromStdString(debug_string); + bool ok = printer.PrintToString(message, &debug_string); + if (ok) { + return QString::number(size) + " bytes " + QString::fromStdString(debug_string); + } else { + return "[could not convert message to string]"; + } } diff --git a/servatrice/src/isl_interface.cpp b/servatrice/src/isl_interface.cpp index bcf71a98a..692a0fdba 100644 --- a/servatrice/src/isl_interface.cpp +++ b/servatrice/src/isl_interface.cpp @@ -3,6 +3,7 @@ #include "main.h" #include "server_logger.h" +#include #include #include #include @@ -21,6 +22,8 @@ #include #include +inline Q_LOGGING_CATEGORY(IslInterfaceLog, "isl_interface"); + void IslInterface::sharedCtor(const QSslCertificate &cert, const QSslKey &privateKey) { socket = new QSslSocket(this); @@ -113,10 +116,11 @@ void IslInterface::initServer() socket->startServerEncryption(); if (!socket->waitForEncrypted(5000)) { QList sslErrors(socket->sslHandshakeErrors()); - if (sslErrors.isEmpty()) - qDebug() << "[ISL] SSL handshake timeout, terminating connection"; - else - qDebug() << "[ISL] SSL errors:" << sslErrors; + if (sslErrors.isEmpty()) { + qCDebug(IslInterfaceLog) << "SSL handshake timeout, terminating connection"; + } else { + qCWarning(IslInterfaceLog) << "SSL errors:" << sslErrors; + } deleteLater(); return; } @@ -157,7 +161,7 @@ void IslInterface::initServer() server->islLock.lockForWrite(); if (server->islConnectionExists(serverId)) { - qDebug() << "[ISL] Duplicate connection to #" << serverId << "terminating connection"; + qCDebug(IslInterfaceLog) << "Duplicate connection to #" << serverId << "terminating connection"; deleteLater(); } else { transmitMessage(message); @@ -180,27 +184,28 @@ void IslInterface::initClient() expectedErrors.append(QSslError(QSslError::SelfSignedCertificate, peerCert)); socket->ignoreSslErrors(expectedErrors); - qDebug() << "[ISL] Connecting to #" << serverId << ":" << peerAddress << ":" << peerPort; + qCDebug(IslInterfaceLog) << "Connecting to #" << serverId << ":" << peerAddress << ":" << peerPort; socket->connectToHostEncrypted(peerAddress, peerPort, peerHostName); if (!socket->waitForConnected(5000)) { - qDebug() << "[ISL] Socket error:" << socket->errorString(); + qCDebug(IslInterfaceLog) << "Socket error:" << socket->errorString(); deleteLater(); return; } if (!socket->waitForEncrypted(5000)) { QList sslErrors(socket->sslHandshakeErrors()); - if (sslErrors.isEmpty()) - qDebug() << "[ISL] SSL handshake timeout, terminating connection"; - else - qDebug() << "[ISL] SSL errors:" << sslErrors; + if (sslErrors.isEmpty()) { + qCDebug(IslInterfaceLog) << "SSL handshake timeout, terminating connection"; + } else { + qCWarning(IslInterfaceLog) << "SSL errors:" << sslErrors; + } deleteLater(); return; } server->islLock.lockForWrite(); if (server->islConnectionExists(serverId)) { - qDebug() << "[ISL] Duplicate connection to #" << serverId << "terminating connection"; + qCDebug(IslInterfaceLog) << "Duplicate connection to #" << serverId << "terminating connection"; deleteLater(); return; } @@ -242,17 +247,21 @@ void IslInterface::readClient() return; IslMessage newMessage; - newMessage.ParseFromArray(inputBuffer.data(), messageLength); + bool ok = newMessage.ParseFromArray(inputBuffer.data(), messageLength); inputBuffer.remove(0, messageLength); messageInProgress = false; - processMessage(newMessage); + if (ok) { + processMessage(newMessage); + } else { + qCWarning(IslInterfaceLog) << "parsing error!"; + } } while (!inputBuffer.isEmpty()); } void IslInterface::catchSocketError(QAbstractSocket::SocketError socketError) { - qDebug() << "[ISL] Socket error:" << socketError; + qCWarning(IslInterfaceLog) << "Socket error:" << socketError; server->islLock.lockForWrite(); server->removeIslInterface(serverId); @@ -270,7 +279,10 @@ void IslInterface::transmitMessage(const IslMessage &item) unsigned int size = static_cast(item.ByteSize()); #endif buf.resize(size + 4); - item.SerializeToArray(buf.data() + 4, size); + if (!item.SerializeToArray(buf.data() + 4, size)) { + qCWarning(IslInterfaceLog) << "transmit error!"; + return; + } buf.data()[3] = (unsigned char)size; buf.data()[2] = (unsigned char)(size >> 8); buf.data()[1] = (unsigned char)(size >> 16); @@ -368,7 +380,7 @@ void IslInterface::processSessionEvent(const SessionEvent &event, qint64 session QReadLocker clientsLocker(&server->clientsLock); Server_AbstractUserInterface *client = server->getUsersBySessionId().value(sessionId); if (!client) { - qDebug() << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; + qCDebug(IslInterfaceLog) << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; break; } const Event_GameJoined &gameJoined = event.GetExtension(Event_GameJoined::ext); @@ -382,7 +394,8 @@ void IslInterface::processSessionEvent(const SessionEvent &event, qint64 session QReadLocker clientsLocker(&server->clientsLock); Server_AbstractUserInterface *client = server->getUsersBySessionId().value(sessionId); if (!client) { - qDebug() << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; + qCWarning(IslInterfaceLog) + << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; break; } @@ -430,7 +443,7 @@ void IslInterface::processRoomCommand(const CommandContainer &cont, qint64 sessi void IslInterface::processMessage(const IslMessage &item) { - qDebug() << getSafeDebugString(item); + qCDebug(IslInterfaceLog) << getSafeDebugString(item); switch (item.message_type()) { case IslMessage::ROOM_COMMAND_CONTAINER: { diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index fa9b14f31..bce3542e8 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -7,12 +7,15 @@ #include #include #include +#include #include #include #include #include #include +inline Q_LOGGING_CATEGORY(DatabaseInterfaceLog, "database_interface"); + Servatrice_DatabaseInterface::Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server) : instanceId(_instanceId), sqlDatabase(QSqlDatabase()), server(_server) { @@ -56,17 +59,16 @@ bool Servatrice_DatabaseInterface::openDatabase() sqlDatabase.close(); const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); - qDebug().noquote() << QString("[%1] Opening database...").arg(poolStr); + qCDebug(DatabaseInterfaceLog).noquote() << poolStr << "Opening database..."; if (!sqlDatabase.open()) { - qCritical() << QString("[%1] Error opening database: %2").arg(poolStr).arg(sqlDatabase.lastError().text()); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error opening database:" << sqlDatabase.lastError().text(); return false; } QSqlQuery *versionQuery = prepareQuery("select version from {prefix}_schema_version limit 1"); if (!execSqlQuery(versionQuery)) { - qCritical() << QString("[%1] Error opening database: unable to load database schema version (hint: ensure the " - "cockatrice_schema_version exists)") - .arg(poolStr); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error opening database: unable to load database schema version" + << "(hint: ensure the cockatrice_schema_version exists)"; return false; } @@ -74,24 +76,21 @@ bool Servatrice_DatabaseInterface::openDatabase() const int dbversion = versionQuery->value(0).toInt(); const int expectedversion = DATABASE_SCHEMA_VERSION; if (dbversion < expectedversion) { - qCritical() << QString("[%1] Error opening database: the database schema version is too old, you need to " - "run the migrations to update it from version %2 to version %3") - .arg(poolStr) - .arg(dbversion) - .arg(expectedversion); + qCCritical(DatabaseInterfaceLog) << poolStr + << "Error opening database: the database schema version is too old, you " + "need to run the migrations to update it from version" + << dbversion << "to version" << expectedversion; return false; } else if (dbversion > expectedversion) { - qCritical() << QString("[%1] Error opening database: the database schema version %2 is too new, you need " - "to update servatrice (this servatrice actually uses version %3)") - .arg(poolStr) - .arg(dbversion) - .arg(expectedversion); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error opening database: the database schema version" + << dbversion << "is too new, you need to update servatrice" + << "(this servatrice actually uses version" << expectedversion << ")"; return false; } } else { - qCritical() << QString("[%1] Error opening database: unable to load database schema version (hint: ensure the " - "cockatrice_schema_version contains a single record)") - .arg(poolStr); + qCCritical(DatabaseInterfaceLog) << poolStr + << "Error opening database: unable to load database schema version (hint: " + "ensure the cockatrice_schema_version contains a single record)"; return false; } @@ -114,9 +113,7 @@ bool Servatrice_DatabaseInterface::checkSql() if (query.lastError().isValid()) { const auto &poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); - qCritical() << QString("[%1] Error executing query: %2, resetting connection") - .arg(poolStr) - .arg(query.lastError().text()); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error executing query:" << query.lastError().text(); sqlDatabase.close(); return openDatabase(); @@ -145,7 +142,7 @@ bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery *query) if (query->exec()) return true; const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); - qCritical() << QString("[%1] Error executing query: %2").arg(poolStr).arg(query->lastError().text()); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error executing query:" << query->lastError().text(); sqlDatabase.close(); openDatabase(); return false; @@ -252,7 +249,8 @@ bool Servatrice_DatabaseInterface::registerUser(const QString &userName, query->bindValue(":token", token); if (!execSqlQuery(query)) { - qDebug() << "Failed to insert user: " << query->lastError() << " sql: " << query->lastQuery(); + qCWarning(DatabaseInterfaceLog) << "Failed to insert user: " << query->lastError() + << " sql: " << query->lastQuery(); return false; } @@ -270,8 +268,8 @@ bool Servatrice_DatabaseInterface::activateUser(const QString &userName, const Q activateQuery->bindValue(":username", userName); activateQuery->bindValue(":token", token); if (!execSqlQuery(activateQuery)) { - qDebug() << "Account activation failed: SQL error." << activateQuery->lastError() - << " sql: " << activateQuery->lastQuery(); + qCWarning(DatabaseInterfaceLog) << "Account activation failed: SQL error." << activateQuery->lastError() + << " sql: " << activateQuery->lastQuery(); return false; } @@ -284,7 +282,8 @@ bool Servatrice_DatabaseInterface::activateUser(const QString &userName, const Q query->bindValue(":userName", userName); if (!execSqlQuery(query)) { - qDebug() << "Failed to activate user: " << query->lastError() << " sql: " << query->lastQuery(); + qCWarning(DatabaseInterfaceLog) + << "Failed to activate user: " << query->lastError() << " sql: " << query->lastQuery(); return false; } @@ -326,7 +325,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot prepareQuery("select password_sha512, active from {prefix}_users where name = :name"); passwordQuery->bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { - qDebug("Login denied: SQL error"); + qCWarning(DatabaseInterfaceLog) << "Login denied: SQL error"; return NotLoggedIn; } @@ -334,7 +333,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot const QString correctPasswordSha512 = passwordQuery->value(0).toString(); const bool userIsActive = passwordQuery->value(1).toBool(); if (!userIsActive) { - qDebug("Login denied: user not active"); + qCWarning(DatabaseInterfaceLog) << "Login denied: user not active"; return UserIsInactive; } QString hashedPassword; @@ -344,14 +343,14 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot hashedPassword = password; } if (correctPasswordSha512 == hashedPassword) { - qDebug("Login accepted: password right"); + qCDebug(DatabaseInterfaceLog) << "Login accepted: password right"; return PasswordRight; } else { - qDebug("Login denied: password wrong"); + qCDebug(DatabaseInterfaceLog) << "Login denied: password wrong"; return NotLoggedIn; } } else { - qDebug("Login accepted: unknown user"); + qCDebug(DatabaseInterfaceLog) << "Login accepted: unknown user"; return UnknownUser; } } @@ -369,7 +368,7 @@ bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, return false; if (!checkSql()) { - qDebug("Failed to check if user is banned. Database invalid."); + qCWarning(DatabaseInterfaceLog) << "Failed to check if user is banned. Database invalid."; return false; } @@ -400,7 +399,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIdBanned(const QString &clientId, idBanQuery->bindValue(":id", clientId); idBanQuery->bindValue(":id2", clientId); if (!execSqlQuery(idBanQuery)) { - qDebug() << "Id ban check failed: SQL error." << idBanQuery->lastError(); + qCWarning(DatabaseInterfaceLog) << "Id ban check failed: SQL error." << idBanQuery->lastError(); return false; } @@ -410,7 +409,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIdBanned(const QString &clientId, if ((secondsLeft > 0) || permanentBan) { banReason = idBanQuery->value(2).toString(); banSecondsRemaining = permanentBan ? 0 : secondsLeft; - qDebug() << "User is banned by client id" << clientId; + qCDebug(DatabaseInterfaceLog) << "User is banned by client id" << clientId; return true; } } @@ -428,7 +427,7 @@ bool Servatrice_DatabaseInterface::checkUserIsNameBanned(const QString &userName nameBanQuery->bindValue(":name1", userName); nameBanQuery->bindValue(":name2", userName); if (!execSqlQuery(nameBanQuery)) { - qDebug() << "Name ban check failed: SQL error" << nameBanQuery->lastError(); + qCWarning(DatabaseInterfaceLog) << "Name ban check failed: SQL error" << nameBanQuery->lastError(); return false; } @@ -438,7 +437,7 @@ bool Servatrice_DatabaseInterface::checkUserIsNameBanned(const QString &userName if ((secondsLeft > 0) || permanentBan) { banReason = nameBanQuery->value(2).toString(); banSecondsRemaining = permanentBan ? 0 : secondsLeft; - qDebug() << "Username" << userName << "is banned by name"; + qCDebug(DatabaseInterfaceLog) << "Username" << userName << "is banned by name"; return true; } } @@ -464,7 +463,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, ipBanQuery->bindValue(":address", ipAddress); ipBanQuery->bindValue(":address2", ipAddress); if (!execSqlQuery(ipBanQuery)) { - qDebug() << "IP ban check failed: SQL error." << ipBanQuery->lastError(); + qCWarning(DatabaseInterfaceLog) << "IP ban check failed: SQL error." << ipBanQuery->lastError(); return false; } @@ -474,7 +473,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, if ((secondsLeft > 0) || permanentBan) { banReason = ipBanQuery->value(2).toString(); banSecondsRemaining = permanentBan ? 0 : secondsLeft; - qDebug() << "User is banned by address" << ipAddress; + qCDebug(DatabaseInterfaceLog) << "User is banned by address" << ipAddress; return true; } } @@ -865,12 +864,17 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, const unsigned int size = static_cast(replayList[i]->ByteSize()); #endif blob.resize(size); - replayList[i]->SerializeToArray(blob.data(), size); + qulonglong replayId = replayList[i]->replay_id(); + if (replayList[i]->SerializeToArray(blob.data(), size)) { - replayIds.append(QVariant((qulonglong)replayList[i]->replay_id())); - replayGameIds.append(gameInfo.game_id()); - replayDurations.append(replayList[i]->duration_seconds()); - replayBlobs.append(blob); + replayIds.append(QVariant(replayId)); + replayGameIds.append(gameInfo.game_id()); + replayDurations.append(replayList[i]->duration_seconds()); + replayBlobs.append(blob); + } else { + qCWarning(DatabaseInterfaceLog) + << "failed to serialise replay, id:" << replayId << "game:" << gameInfo.game_id(); + } } { @@ -1017,7 +1021,7 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, passwordQuery->bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { - qDebug("Change password denied: SQL error"); + qCWarning(DatabaseInterfaceLog) << "Change password denied: SQL error"; return false; } @@ -1084,7 +1088,7 @@ void Servatrice_DatabaseInterface::updateUsersLastLoginData(const QString &userN QSqlQuery *query = prepareQuery("select id from {prefix}_users where name = :user_name"); query->bindValue(":user_name", userName); if (!execSqlQuery(query)) { - qDebug("Failed to locate user id when updating users last login data: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to locate user id when updating users last login data: SQL Error"; return; } @@ -1133,7 +1137,7 @@ QList Servatrice_DatabaseInterface::getUserBanHistory(const QStr query->bindValue(":user_name", userName); if (!execSqlQuery(query)) { - qDebug("Failed to collect ban history information: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to collect ban history information: SQL Error"; return results; } @@ -1168,7 +1172,7 @@ bool Servatrice_DatabaseInterface::addWarning(const QString userName, query->bindValue(":warn_reason", warningReason); query->bindValue(":client_id", clientID); if (!execSqlQuery(query)) { - qDebug("Failed to collect create warning history information: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to collect create warning history information: SQL Error"; return false; } @@ -1189,7 +1193,7 @@ QList Servatrice_DatabaseInterface::getUserWarnHistory(const query->bindValue(":user_id", userID); if (!execSqlQuery(query)) { - qDebug("Failed to collect warning history information: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to collect warning history information: SQL Error"; return results; } @@ -1296,7 +1300,7 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory } if (!execSqlQuery(query)) { - qDebug("Failed to collect log history information: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to collect log history information: SQL Error"; return results; } @@ -1324,7 +1328,8 @@ int Servatrice_DatabaseInterface::checkNumberOfUserAccounts(const QString &email query->bindValue(":user_email", email); if (!execSqlQuery(query)) { - qDebug("Failed to identify the number of users accounts for users email address: SQL Error"); + qCWarning(DatabaseInterfaceLog) + << "Failed to identify the number of users accounts for users email address: SQL Error"; return 0; } diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 619d36d3a..41e61ddec 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -83,6 +84,10 @@ #include #include +inline Q_LOGGING_CATEGORY(AbstractServerSocketInterfaceLog, "abstract_server_socket_interface"); +inline Q_LOGGING_CATEGORY(TcpServerSocketInterfaceLog, "tcp_server_socket_interface"); +inline Q_LOGGING_CATEGORY(WebsocketServerSocketInterfaceLog, "websocket_server_socket_interface"); + static const int protocolVersion = 14; AbstractServerSocketInterface::AbstractServerSocketInterface(Servatrice *_server, @@ -130,7 +135,7 @@ bool AbstractServerSocketInterface::initSession() void AbstractServerSocketInterface::catchSocketError(QAbstractSocket::SocketError socketError) { - qDebug() << "Socket error:" << socketError; + qCWarning(AbstractServerSocketInterfaceLog) << "Socket error:" << socketError; prepareDestroy(); } @@ -1100,7 +1105,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com clientIdQuery->bindValue(":client_id", nameFromStdString(cmd.clientid())); sqlInterface->execSqlQuery(clientIdQuery); if (!sqlInterface->execSqlQuery(clientIdQuery)) { - qDebug("ClientID username ban lookup failed: SQL Error"); + qCWarning(AbstractServerSocketInterfaceLog) << "ClientID username ban lookup failed: SQL Error"; } else { while (clientIdQuery->next()) { userName = clientIdQuery->value(0).toString(); @@ -1152,7 +1157,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C { QString userName = nameFromStdString(cmd.user_name()); QString clientId = nameFromStdString(cmd.clientid()); - qDebug() << "Got register command for user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Got register command for user:" << userName; bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); if (!registrationEnabled) { @@ -1289,7 +1294,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C country, !requireEmailActivation); if (regSucceeded) { - qDebug() << "Accepted register command for user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Accepted register command for user:" << userName; if (requireEmailActivation) { QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_activation_emails (name) values(:name)"); @@ -1337,7 +1342,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdActivateAccount(const C clientId = "UNKNOWN"; if (sqlInterface->activateUser(userName, token)) { - qDebug() << "Accepted activation for user" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Accepted activation for user" << userName; if (servatrice->getEnableRegistrationAudit()) sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), @@ -1345,7 +1350,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdActivateAccount(const C return Response::RespActivationAccepted; } else { - qDebug() << "Failed activation for user" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Failed activation for user" << userName; if (servatrice->getEnableRegistrationAudit()) sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), @@ -1493,7 +1498,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordRequest(c const QString userName = nameFromStdString(cmd.user_name()); const QString clientId = nameFromStdString(cmd.clientid()); - qDebug() << "Received reset password request from user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Received reset password request from user:" << userName; if (!servatrice->getEnableForgotPassword()) { if (servatrice->getEnableForgotPasswordAudit()) @@ -1575,7 +1580,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordReset(con Q_UNUSED(rc); QString userName = nameFromStdString(cmd.user_name()); QString clientId = nameFromStdString(cmd.clientid()); - qDebug() << "Received reset password reset from user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Received reset password reset from user:" << userName; if (!sqlInterface->doesForgotPasswordExist(userName)) { if (servatrice->getEnableForgotPasswordAudit()) @@ -1626,7 +1631,7 @@ AbstractServerSocketInterface::cmdForgotPasswordChallenge(const Command_ForgotPa const QString userName = nameFromStdString(cmd.user_name()); const QString clientId = nameFromStdString(cmd.clientid()); - qDebug() << "Received reset password challenge from user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Received reset password challenge from user:" << userName; if (!servatrice->getEnableForgotPasswordChallenge()) { if (servatrice->getEnableForgotPasswordAudit()) { @@ -1971,13 +1976,16 @@ void TcpServerSocketInterface::flushOutputQueue() unsigned int size = static_cast(item.ByteSize()); #endif buf.resize(size + 4); - item.SerializeToArray(buf.data() + 4, size); - buf.data()[3] = (unsigned char)size; - buf.data()[2] = (unsigned char)(size >> 8); - buf.data()[1] = (unsigned char)(size >> 16); - buf.data()[0] = (unsigned char)(size >> 24); - // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. - writeToSocket(buf); + if (item.SerializeToArray(buf.data() + 4, size)) { + buf.data()[3] = (unsigned char)size; + buf.data()[2] = (unsigned char)(size >> 8); + buf.data()[1] = (unsigned char)(size >> 16); + buf.data()[0] = (unsigned char)(size >> 24); + // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. + writeToSocket(buf); + } else { + qCWarning(TcpServerSocketInterfaceLog) << "serialisation error!"; + } totalBytes += size + 4; locker.relock(); @@ -2010,41 +2018,48 @@ void TcpServerSocketInterface::readClient() return; CommandContainer newCommandContainer; + bool ok; try { - newCommandContainer.ParseFromArray(inputBuffer.data(), messageLength); + ok = newCommandContainer.ParseFromArray(inputBuffer.data(), messageLength); } catch (std::exception &e) { - qDebug() << "Caught std::exception in" << __FILE__ << __LINE__ << + qCWarning(TcpServerSocketInterfaceLog) << "Caught std::exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio - __FUNCTION__; + __FUNCTION__ #else - __PRETTY_FUNCTION__; + __PRETTY_FUNCTION__ #endif - qDebug() << "Exception:" << e.what(); - qDebug() << "Message coming from:" << getAddress(); - qDebug() << "Message length:" << messageLength; - qDebug() << "Message content:" << inputBuffer.toHex(); + << Qt::endl + << "Exception:" << e.what() << Qt::endl + << "Message coming from:" << getAddress() << Qt::endl + << "Message length:" << messageLength << Qt::endl + << "Message content:" << inputBuffer.toHex(); } catch (...) { - qDebug() << "Unhandled exception in" << __FILE__ << __LINE__ << + qCWarning(TcpServerSocketInterfaceLog) << "Unhandled exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio - __FUNCTION__; + __FUNCTION__ #else - __PRETTY_FUNCTION__; + __PRETTY_FUNCTION__ #endif - qDebug() << "Message coming from:" << getAddress(); + << Qt::endl + << "Message coming from:" << getAddress(); } - inputBuffer.remove(0, messageLength); messageInProgress = false; - // dirty hack to make v13 client display the correct error message - if (handshakeStarted) - processCommandContainer(newCommandContainer); - else if (!newCommandContainer.has_cmd_id()) { - handshakeStarted = true; - if (!initTcpSession()) - prepareDestroy(); + if (ok) { + // dirty hack to make v13 client display the correct error message + if (handshakeStarted) + processCommandContainer(newCommandContainer); + else if (!newCommandContainer.has_cmd_id()) { + handshakeStarted = true; + if (!initTcpSession()) + prepareDestroy(); + } + // end of hack + } else { + qCWarning(TcpServerSocketInterfaceLog) << "parsing error!"; } - // end of hack + } while (!inputBuffer.isEmpty()); } @@ -2172,9 +2187,12 @@ void WebsocketServerSocketInterface::flushOutputQueue() unsigned int size = static_cast(item.ByteSize()); #endif buf.resize(size); - item.SerializeToArray(buf.data(), size); - // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. - writeToSocket(buf); + if (item.SerializeToArray(buf.data(), size)) { + // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. + writeToSocket(buf); + } else { + qCWarning(TcpServerSocketInterfaceLog) << "serialisation error!"; + } totalBytes += size; locker.relock(); @@ -2190,30 +2208,37 @@ void WebsocketServerSocketInterface::binaryMessageReceived(const QByteArray &mes servatrice->incRxBytes(message.size()); CommandContainer newCommandContainer; + bool ok; try { - newCommandContainer.ParseFromArray(message.data(), message.size()); + ok = newCommandContainer.ParseFromArray(message.data(), message.size()); } catch (std::exception &e) { - qDebug() << "Caught std::exception in" << __FILE__ << __LINE__ << + qCWarning(WebsocketServerSocketInterfaceLog) << "Caught std::exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio - __FUNCTION__; + __FUNCTION__ #else - __PRETTY_FUNCTION__; + __PRETTY_FUNCTION__ #endif - qDebug() << "Exception:" << e.what(); - qDebug() << "Message coming from:" << getAddress(); - qDebug() << "Message length:" << message.size(); - qDebug() << "Message content:" << message.toHex(); + << Qt::endl + << "Exception:" << e.what() << Qt::endl + << "Message coming from:" << getAddress() << Qt::endl + << "Message length:" << message.size() << Qt::endl + << "Message content:" << message.toHex(); } catch (...) { - qDebug() << "Unhandled exception in" << __FILE__ << __LINE__ << + qCWarning(WebsocketServerSocketInterfaceLog) << "Unhandled exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio - __FUNCTION__; + __FUNCTION__ #else - __PRETTY_FUNCTION__; + __PRETTY_FUNCTION__ #endif - qDebug() << "Message coming from:" << getAddress(); + << Qt::endl + << "Message coming from:" << getAddress(); } - processCommandContainer(newCommandContainer); + if (ok) { + processCommandContainer(newCommandContainer); + } else { + qCWarning(WebsocketServerSocketInterfaceLog) << "parsing error!"; + } } bool AbstractServerSocketInterface::isPasswordLongEnough(const int passwordLength) From dbed6890dac7b308ef228362e63cb67413c14fcd Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:36:45 -0300 Subject: [PATCH 26/60] feat: support expressions when setting card counters (#6753) * feat: enable expressions in card counters * fix includes * fix multiple selection * cleanup useless conversions * const ref where possible * do not use const, be consistent with local patterns in the file --- cockatrice/src/game/player/player_actions.cpp | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index ca0967636..a44c28d40 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -3,6 +3,7 @@ #include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/utility/get_text_with_max.h" #include "../board/card_item.h" +#include "../client/settings/card_counter_settings.h" #include "../dialogs/dlg_move_top_cards_until.h" #include "../dialogs/dlg_roll_dice.h" #include "../zones/hand_zone.h" @@ -27,6 +28,7 @@ #include #include #include +#include #include #include @@ -1572,23 +1574,34 @@ void PlayerActions::actCardCounterTrigger() break; } case 11: { // set counter with dialog - bool ok; player->setDialogSemaphore(true); - int oldValue = 0; - if (player->getGameScene()->selectedItems().size() == 1) { - auto *card = static_cast(player->getGameScene()->selectedItems().first()); - oldValue = card->getCounters().value(counterId, 0); + // If a single card is selected, we show the old value in the dialog. Otherwise, we show "x" + QList sel = player->getGameScene()->selectedItems(); + QString oldValueForDlg = "x"; + if (sel.size() == 1) { + auto *card = dynamic_cast(sel.first()); + oldValueForDlg = QString::number(card->getCounters().value(counterId, 0)); } - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Set counters"), tr("Number:"), oldValue, - 0, MAX_COUNTERS_ON_CARD, 1, &ok); + + auto &cardCounterSettings = SettingsCache::instance().cardCounters(); + QString counterName = cardCounterSettings.displayName(counterId); + + AbstractCounterDialog dialog(counterName, oldValueForDlg, player->getGame()->getTab()); + int ok = dialog.exec(); + player->setDialogSemaphore(false); if (player->clearCardsToDelete() || !ok) { return; } - for (const auto &item : player->getGameScene()->selectedItems()) { - auto *card = static_cast(item); + for (const auto &item : sel) { + auto *card = dynamic_cast(item); + + int oldValue = card->getCounters().value(counterId, 0); + Expression exp(oldValue); + int number = static_cast(exp.parse(dialog.textValue())); + auto *cmd = new Command_SetCardCounter; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); From 335022c4aa3ebda39eb0f42fbfc3728cc2b831aa Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:02:00 +0200 Subject: [PATCH 27/60] Include themes (#6781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Properly reset default theme. Took 9 minutes * Descend in preference on windows. Took 9 minutes * Also reset style for custom themes. Took 3 minutes * Try things Took 9 minutes * Add modern windows style. Took 8 minutes * Update theme_manager.cpp --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 1ca3c77c2..12733afe6 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -564,6 +564,9 @@ if(WIN32) PATTERN "styles/qopensslbackend.dll" PATTERN "styles/qschannelbackend.dll" PATTERN "styles/qwindowsvistastyle.dll" + PATTERN "styles/qwindows11style.dll" + PATTERN "styles/qmodernwindowsstyle.dll" + PATTERN "styles/qmodernwindowsstyled.dll" PATTERN "tls/qcertonlybackend.dll" PATTERN "tls/qopensslbackend.dll" PATTERN "tls/qschannelbackend.dll" From 635fae101d2d5c97a52d774b90ffa6ffebd6f244 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:57:03 +0200 Subject: [PATCH 28/60] Use palette colors for resizable panel stylesheets. (#6782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 7 minutes Took 5 seconds Co-authored-by: Lukas Brübach --- .../deck_analytics/resizable_panel.cpp | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp index a0c971a75..f7bb5ac35 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp @@ -20,7 +20,6 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid frame = new QFrame(this); frame->setFrameShape(QFrame::Box); frame->setLineWidth(2); - frame->setStyleSheet("border: none;"); auto *frameLayout = new QVBoxLayout(frame); frameLayout->setContentsMargins(0, 0, 0, 0); @@ -30,15 +29,13 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid frameLayout->addWidget(analyticsPanel); dropIndicator = new QFrame(frame); - dropIndicator->setStyleSheet("background-color: #3daee9;"); dropIndicator->setFixedHeight(3); dropIndicator->hide(); // hidden by default dropIndicator->raise(); // make sure it's above children selectionOverlay = new QFrame(frame); - selectionOverlay->setStyleSheet("background-color: rgba(61,174,233,50);"); // semi-transparent blue - selectionOverlay->hide(); // hidden by default - selectionOverlay->raise(); // make sure it is above children + selectionOverlay->hide(); // hidden by default + selectionOverlay->raise(); // make sure it is above children selectionOverlay->setAttribute(Qt::WA_TransparentForMouseEvents); // Bottom bar with drag button and resize handle @@ -51,24 +48,41 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid dragButton = new QPushButton("☰", bottomBar); dragButton->setFixedSize(40, 8); dragButton->setCursor(Qt::OpenHandCursor); - dragButton->setStyleSheet("QPushButton { " - "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4a4a4a, stop:1 #3a3a3a); " - "border: none; color: #888; font-size: 10px; }" - "QPushButton:hover { background: #5a5a5a; }"); bottomLayout->addWidget(dragButton); // Resize handle fills the rest resizeHandle = new QWidget(bottomBar); resizeHandle->setFixedHeight(8); resizeHandle->setCursor(Qt::SizeVerCursor); - resizeHandle->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " - "stop:0 #3a3a3a, stop:1 #2a2a2a);"); bottomLayout->addWidget(resizeHandle, 1); frameLayout->addWidget(bottomBar); mainLayout->addWidget(frame); + const QPalette &pal = QApplication::palette(); + QColor mid = pal.color(QPalette::Mid); + QColor dark = pal.color(QPalette::Dark); + QColor midLight = pal.color(QPalette::Midlight); + QColor shadow = pal.color(QPalette::Shadow); + QColor placeholderText = pal.color(QPalette::PlaceholderText); + + frame->setStyleSheet("QFrame { border: none; }"); + + dropIndicator->setStyleSheet("QFrame { background-color: #3daee9; }"); + + selectionOverlay->setStyleSheet("QFrame { background-color: rgba(61,174,233,50); }"); + + dragButton->setStyleSheet(QString("QPushButton { " + "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %2); " + "border: none; color: %3; font-size: 10px; }" + "QPushButton:hover { background: %4; }") + .arg(mid.name(), dark.name(), placeholderText.name(), midLight.name())); + + resizeHandle->setStyleSheet(QString("QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " + "stop:0 %1, stop:1 %2); }") + .arg(dark.name(), shadow.name())); + // Set size policy setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); From d7b31f2f9d87ba8e9cfa563cd95229a922db1567 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:58:42 +0200 Subject: [PATCH 29/60] Set OracleWizard style to "Modern" instead of "Aero" (#6778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use fusions own palette. Took 6 minutes * Start from default palette always. Took 4 minutes * Add modern style. Took 24 seconds * Scope this fix to Windows. Took 4 minutes --------- Co-authored-by: Lukas Brübach --- cockatrice/src/interface/theme_manager.cpp | 14 ++++++++------ oracle/src/oraclewizard.cpp | 4 ++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 1dc61fdb7..5809ce350 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -179,7 +179,7 @@ QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush) static inline QPalette createDarkGreenFusionPalette() { - QPalette p; + QPalette p = QStyleFactory::create("Fusion")->standardPalette(); // ---------- Core backgrounds ---------- p.setColor(QPalette::Window, QColor(30, 30, 30)); // #ff1e1e1e @@ -248,7 +248,7 @@ static inline QPalette createDarkGreenFusionPalette() static inline QPalette createLightGreenFusionPalette() { - QPalette p; + QPalette p = QStyleFactory::create("Fusion")->standardPalette(); // ---------- Core backgrounds ---------- p.setColor(QPalette::Window, QColor(240, 240, 240)); // #fff0f0f0 @@ -332,13 +332,15 @@ void ThemeManager::themeChangedSlot() } if (themeName == FUSION_THEME_NAME) { - qApp->setStyle(QStyleFactory::create("Fusion")); + QStyle *fusionStyle = QStyleFactory::create("Fusion"); + qApp->setStyle(fusionStyle); #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) - QPalette palette; + // Start from Fusion's own palette so dark mode is handled correctly, + // then apply any tweaks on top of it. + QPalette palette = fusionStyle->standardPalette(); if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) { palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); } - qApp->setPalette(palette); #endif } else if (themeName == FUSION_THEME_NAME_LIGHT) { @@ -348,7 +350,7 @@ void ThemeManager::themeChangedSlot() qApp->setStyle(QStyleFactory::create("Fusion")); qApp->setPalette(createDarkGreenFusionPalette()); } else { - qApp->setStyle(defaultStyleName); // setting the style also sets the palette + qApp->setStyle(QStyleFactory::create(defaultStyleName)); // setting the style also sets the palette } if (dirPath.isEmpty()) { diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index 9d32a993b..2edb9e561 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -21,6 +21,10 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) // define a dummy context that will be used where needed QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); +#ifdef Q_OS_WIN + setWizardStyle(QWizard::ModernStyle); +#endif + QString oracleSettingsFile = SettingsCache::instance().getSettingsPath() + "oracle.ini"; settings = new QSettings(oracleSettingsFile, QSettings::IniFormat, this); From ac06fb9d1c945a311c1824bdaafc1ee99525ed9a Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:02:00 -0700 Subject: [PATCH 30/60] [Game] Fix crash by properly parenting QObjects (#6788) --- cockatrice/src/game/abstract_game.cpp | 3 ++- cockatrice/src/game/player/menu/player_menu.cpp | 2 +- cockatrice/src/game/player/menu/player_menu.h | 2 +- cockatrice/src/game/player/player_actions.cpp | 3 ++- cockatrice/src/game/player/player_event_handler.cpp | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cockatrice/src/game/abstract_game.cpp b/cockatrice/src/game/abstract_game.cpp index 505cd5fde..9216f9174 100644 --- a/cockatrice/src/game/abstract_game.cpp +++ b/cockatrice/src/game/abstract_game.cpp @@ -1,8 +1,9 @@ #include "abstract_game.h" +#include "../interface/widgets/tabs/tab_game.h" #include "player/player.h" -AbstractGame::AbstractGame(TabGame *_tab) : tab(_tab) +AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab) { gameMetaInfo = new GameMetaInfo(this); gameEventHandler = new GameEventHandler(this); diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp index 7786ec3fc..0dc381e28 100644 --- a/cockatrice/src/game/player/menu/player_menu.cpp +++ b/cockatrice/src/game/player/menu/player_menu.cpp @@ -10,7 +10,7 @@ #include -PlayerMenu::PlayerMenu(Player *_player) : player(_player) +PlayerMenu::PlayerMenu(Player *_player) : QObject(_player), player(_player) { playerMenu = new TearOffMenu(); diff --git a/cockatrice/src/game/player/menu/player_menu.h b/cockatrice/src/game/player/menu/player_menu.h index 5fce27158..104c5a930 100644 --- a/cockatrice/src/game/player/menu/player_menu.h +++ b/cockatrice/src/game/player/menu/player_menu.h @@ -37,7 +37,7 @@ private slots: void refreshShortcuts(); public: - PlayerMenu(Player *player); + explicit PlayerMenu(Player *player); /// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters(). void retranslateUi(); diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index a44c28d40..20034df16 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -35,7 +35,8 @@ // milliseconds in between triggers of the move top cards until action static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100; -PlayerActions::PlayerActions(Player *_player) : player(_player), lastTokenTableRow(0), movingCardsUntil(false) +PlayerActions::PlayerActions(Player *_player) + : QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false) { moveTopCardTimer = new QTimer(this); moveTopCardTimer->setInterval(MOVE_TOP_CARD_UNTIL_INTERVAL); diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index f4c3840e0..5571010d1 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -32,7 +32,7 @@ #include #include -PlayerEventHandler::PlayerEventHandler(Player *_player) : player(_player) +PlayerEventHandler::PlayerEventHandler(Player *_player) : QObject(_player), player(_player) { } From 2d412bfe5266bb8c5ae00ff11b5baf1718bd0767 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Thu, 9 Apr 2026 21:16:38 +0200 Subject: [PATCH 31/60] fix cardloading on qt5 (#6790) --- .../src/interface/card_picture_loader/card_picture_loader.cpp | 1 + libcockatrice_card/libcockatrice/card/printing/exact_card.h | 1 + 2 files changed, 2 insertions(+) diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp index 06a0476c9..dbd51b973 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp @@ -28,6 +28,7 @@ CardPictureLoader::CardPictureLoader() : QObject(nullptr) connect(&SettingsCache::instance(), &SettingsCache::picDownloadChanged, this, &CardPictureLoader::picDownloadChanged); + qRegisterMetaType(); connect(worker, &CardPictureLoaderWorker::imageLoaded, this, &CardPictureLoader::imageLoaded); statusBar = new CardPictureLoaderStatusBar(nullptr); diff --git a/libcockatrice_card/libcockatrice/card/printing/exact_card.h b/libcockatrice_card/libcockatrice/card/printing/exact_card.h index 01c61a3a1..74fc75da3 100644 --- a/libcockatrice_card/libcockatrice/card/printing/exact_card.h +++ b/libcockatrice_card/libcockatrice/card/printing/exact_card.h @@ -114,5 +114,6 @@ public: */ void emitPixmapUpdated() const; }; +Q_DECLARE_METATYPE(ExactCard) #endif // EXACT_CARD_H From 9aa5702e14bcf17f72faa5e24d3eb16ee4b83bec Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:27:14 +0200 Subject: [PATCH 32/60] Translate cockatrice_en@source.ts in en_US (#6783) 100% translated source file: 'cockatrice_en@source.ts' on 'en_US'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- cockatrice/translations/cockatrice_en_US.ts | 2435 +++++++++++-------- 1 file changed, 1356 insertions(+), 1079 deletions(-) diff --git a/cockatrice/translations/cockatrice_en_US.ts b/cockatrice/translations/cockatrice_en_US.ts index c74e63114..254ead590 100644 --- a/cockatrice/translations/cockatrice_en_US.ts +++ b/cockatrice/translations/cockatrice_en_US.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Refresh - + Parse Set Name and Number (if available) Parse Set Name and Number (if available) @@ -36,62 +36,62 @@ AbstractTabDeckEditor - + Open in new tab Open in new tab - + Are you sure? Are you sure? - + The decklist has been modified. Do you want to save the changes? The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error Error - + Could not open deck at %1 Could not open deck at %1 - + Could not save remote deck Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. The deck could not be saved. Please check that the directory is writable and try again. - + Save deck Save deck - + The deck could not be saved. The deck could not be saved. - + There are no cards in your deck to be exported There are no cards in your deck to be exported @@ -133,202 +133,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds seconds - + Error Error - + Could not create themes directory at '%1'. Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - Confirm Change - Confirm Change - - - + Theme settings Theme settings - + Current theme: Current theme: - + Open themes folder Open themes folder - + Home tab background source: Home tab background source: - + Home tab background shuffle frequency: Home tab background shuffle frequency: - + Disabled Disabled - + + Display card name of background in bottom right: + Display card name of background in bottom right: + + + Menu settings Menu settings - + Show keyboard shortcuts in right-click menus Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab Show game filter toolbar above list in room tab - + Card rendering Card rendering - + Display card names on cards having a picture Display card names on cards having a picture - + Auto-Rotate cards with sideways layout Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Scale cards on mouse over - + Use rounded card corners Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: Maximum initial height for card view window: - - + + rows rows - + Maximum expanded height for card view window: Maximum expanded height for card view window: - + Card counters Card counters - + Counter %1 Counter %1 - + Hand layout Hand layout - + Display hand horizontally (wastes space) Display hand horizontally (wastes space) - + Enable left justification Enable left justification - + Table grid layout Table grid layout - + Invert vertical coordinate Invert vertical coordinate - + Minimum player count for multi-column layout: Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: Maximum font size for information displayed on cards: @@ -336,7 +302,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + Back to results + + + Open Deck in Deck Editor Open Deck in Deck Editor @@ -656,22 +627,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards View related cards - + Add card to deck Add card to deck - + Mainboard Mainboard - + Sideboard Sideboard @@ -707,124 +678,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... Re&veal to... - + &All players &All players - + View related cards View related cards - + Token: Token: - + All tokens All tokens - + &Select All &Select All - + S&elect Row S&elect Row - + S&elect Column S&elect Column - + &Play &Play - + &Hide &Hide - + Play &Face Down Play &Face Down - + &Tap / Untap Turn sideways or back again &Tap / Untap - - Toggle &normal untapping - Toggle &normal untapping + + Skip &untapping + Skip &untapping - + T&urn Over Turn face up/face down T&urn Over - + &Peek at card face &Peek at card face - + &Clone &Clone - + Attac&h to card... Attac&h to card... - + Unattac&h Unattac&h - + &Draw arrow... &Draw arrow... - + &Set annotation... &Set annotation... - + Ca&rd counters Ca&rd counters - + &Add counter (%1) &Add counter (%1) - + &Remove counter (%1) &Remove counter (%1) - + &Set counters (%1)... &Set counters (%1)... @@ -840,133 +811,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative their hand - + %1's hand nominative %1's hand - + their library look at zone their library - + %1's library look at zone %1's library - + of their library top cards of zone, of their library - + of %1's library top cards of zone of %1's library - + their library reveal zone their library - + %1's library reveal zone %1's library - + their library shuffle their library - + %1's library shuffle %1's library - - - their library - nominative - their library - - - - %1's library - nominative - %1's library - + their library + nominative + their library + + + + %1's library + nominative + %1's library + + + their graveyard nominative their graveyard - + %1's graveyard nominative %1's graveyard - + their exile nominative their exile - + %1's exile nominative %1's exile - - - their sideboard - look at zone - their sideboard - - - - %1's sideboard - look at zone - %1's sideboard - their sideboard - nominative + look at zone their sideboard %1's sideboard + look at zone + %1's sideboard + + + + their sideboard + nominative + their sideboard + + + + %1's sideboard nominative %1's sideboard - + their custom zone '%1' nominative their custom zone '%1' - + %1's custom zone '%2' nominative %1's custom zone '%2' @@ -1028,7 +999,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database Card Database @@ -1036,7 +1007,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info Card Info @@ -1059,32 +1030,32 @@ This is only saved for moderators and cannot be seen by the banned person.Add to Sideboard - + Select Printing Select Printing - + Show on EDHRec (Commander) Show on EDHRec (Commander) - + Show on EDHRec (Card) Show on EDHRec (Card) - + Show Related cards Show Related cards - + Add card to &maindeck Add card to &maindeck - + Add card to &sideboard Add card to &sideboard @@ -1092,32 +1063,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... Loading Database... - + Banner Card Banner Card - + Main Type Main Type - + Mana Cost Mana Cost - + Colors Colors - + Select Printing Select Printing @@ -1190,17 +1161,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters Filters - + &Clear all filters &Clear all filters - + Delete selected Delete selected @@ -1323,7 +1294,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector Printing Selector @@ -1331,166 +1302,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - - + + Success Success - + Download URLs have been reset. Download URLs have been reset. - + Downloaded card pictures have been reset. Downloaded card pictures have been reset. - + Error Error - + One or more downloaded card pictures could not be cleared. One or more downloaded card pictures could not be cleared. - + Add URL Add URL - - + + URL: URL: - - + + Edit URL Edit URL - + Network Cache Size: Network Cache Size: - + Redirect Cache TTL: Redirect Cache TTL: - + How long cached redirects for urls are valid for. How long cached redirects for urls are valid for. - + Picture Cache Size: Picture Cache Size: - + Add New URL Add New URL - + Remove URL Remove URL - + Day(s) Day(s) - + Updating... Updating... - + Choose path Choose path - + URL Download Priority URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Download Spoilers Automatically - + Spoiler Location: Spoiler Location: - + Last Change Last Change - + Spoilers download automatically on launch Spoilers download automatically on launch - + Press the button to manually update without relaunching Press the button to manually update without relaunching - + Do not close settings until manual update is complete Do not close settings until manual update is complete - + Download card pictures on the fly Download card pictures on the fly - + How to add a custom URL How to add a custom URL - + Delete Downloaded Images Delete Downloaded Images - + Reset Download URLs Reset Download URLs - + On-disk cache for downloaded pictures On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen In-memory cache for pictures not currently on screen @@ -1498,32 +1469,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo Undo - + Redo Redo - + Undo/Redo history Undo/Redo history - + Click on an entry to revert to that point in the history. Click on an entry to revert to that point in the history. - + [redo] [redo] - + [undo] [undo] @@ -1531,27 +1502,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count Count - + Set Set - + Number Number - + Provider ID Provider ID - + Card Card @@ -1559,12 +1530,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) Common deck formats (%1) - + All files (*.*) All files (*.*) @@ -1661,94 +1632,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card Banner Card - + Open in deck editor Open in deck editor - + Edit Tags Edit Tags - + Rename Deck Rename Deck - + Save Deck to Clipboard Save Deck to Clipboard - + Annotated Annotated - + Annotated (No set info) Annotated (No set info) - + Not Annotated Not Annotated - + Not Annotated (No set info) Not Annotated (No set info) - + Rename File Rename File - + Delete File Delete File - + Set Banner Card Set Banner Card - - + + New name: New name: - - + + Error Error - + Rename failed Rename failed - + Delete file Delete file - + Are you sure you want to delete the selected file? Are you sure you want to delete the selected file? - + Delete failed Delete failed @@ -1781,32 +1752,32 @@ This is only saved for moderators and cannot be seen by the banned person.Set format to %1 - + Added (%1): %2 (%3) %4 Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) Removed "%1" (all copies) - + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Added - + Removed Removed @@ -1873,30 +1844,30 @@ This is only saved for moderators and cannot be seen by the banned person.Sideboard locked - - + + Error Error - + The selected file could not be loaded. The selected file could not be loaded. - + Deck is greater than maximum file size. Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice Cockatrice @@ -2391,17 +2362,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard Edit deck in clipboard - + Error Error - + Invalid deck list. Invalid deck list. @@ -2902,17 +2873,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Load deck from clipboard - + Error Error - + Invalid deck list. Invalid deck list. @@ -2920,45 +2891,45 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 Network error: %1 - + Received empty deck data. Received empty deck data. - + Failed to parse deck data: %1 Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2983,40 +2954,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Load deck + + DlgLocalGameOptions + + + Players: + Players: + + + + General + General + + + + Starting life total: + Starting life total: + + + + Game setup options + Game setup options + + + + Remember settings + Remember settings + + + + Local game options + Local game options + + DlgMoveTopCardsUntil - + Card name (or search expressions): Card name (or search expressions): - + Number of hits: Number of hits: - + Auto play hits Auto play hits - + Put top cards on stack until... Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice Cockatrice - + Invalid filter Invalid filter @@ -3178,12 +3182,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3200,7 +3204,7 @@ You may need to rerun oracle to update your card database. Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -3217,7 +3221,7 @@ Usually this can be fixed by rerunning oracle to to update your card database. Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3230,7 +3234,7 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with you Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3239,7 +3243,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3248,7 +3252,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3261,59 +3265,59 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues Would you like to change your database location setting? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Settings - + General General - + Appearance Appearance - + User Interface User Interface - + Card Sources Card Sources - + Chat Chat - + Sound Sound - + Shortcuts Shortcuts @@ -3630,67 +3634,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability Draw Probability - + Probability of drawing Probability of drawing - + Card Name Card Name - + Type Type - + Subtype Subtype - + Mana Value Mana Value - + At least At least - + Exactly Exactly - + card(s) having drawn at least card(s) having drawn at least - + cards cards - + Category Category - + Qty Qty - + Odds (%) Odds (%) @@ -4138,143 +4142,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reset all paths - + All paths have been reset All paths have been reset - - - - - - - + + + + + + + Choose path Choose path - + Personal settings Personal settings - + Language: Language: - + Paths (editing disabled in portable mode) Paths (editing disabled in portable mode) - + Paths Paths - + How to help with translations How to help with translations - + Decks directory: Decks directory: - + Filters directory: Filters directory: - + Replays directory: Replays directory: - + Pictures directory: Pictures directory: - + Card database: Card database: - + Custom database directory: Custom database directory: - + Token database: Token database: - + Update channel Update channel - + Check for client updates on startup Check for client updates on startup - + Check for card database updates on startup Check for card database updates on startup - + Don't check Don't check - + Prompt for update Prompt for update - + Always update in the background Always update in the background - + Check for card database updates every Check for card database updates every - + days days - + Notify if a feature supported by the server is missing in my client Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Show tips on startup - + Last update check on %1 (%2 days ago) Last update check on %1 (%2 days ago) @@ -4282,47 +4286,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard &Graveyard - + &View graveyard &View graveyard - + &Move graveyard to... &Move graveyard to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &All players &All players - + &Hand &Hand - + &Exile &Exile - + Reveal random card to... Reveal random card to... @@ -4330,88 +4334,88 @@ You may have to manually download the new version. HandMenu - + &Hand &Hand - + &View hand &View hand - + Sort hand by... Sort hand by... - + Name Name - + Type Type - + Mana Value Mana Value - + Take &mulligan (Choose hand size) Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) Take mulligan (Hand size - 1) - + &Move hand to... &Move hand to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &Graveyard &Graveyard - + &Exile &Exile - + &Reveal hand to... &Reveal hand to... - - + + All players All players - + Reveal r&andom card to... Reveal r&andom card to... @@ -4419,52 +4423,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck Create New Deck - + Browse Decks Browse Decks - + Browse Card Database Browse Card Database - + Browse EDHRec Browse EDHRec - + Browse Archidekt Browse Archidekt - + View Replays View Replays - + Quit Quit - + Connecting... Connecting... - + Connect Connect - + Play Play @@ -4472,193 +4476,213 @@ You may have to manually download the new version. LibraryMenu - + &Library &Library - + &View library &View library - + View &top cards of library... View &top cards of library... - + View bottom cards of library... View bottom cards of library... - + Reveal &library to... Reveal &library to... - + Lend library to... Lend library to... - + Reveal &top cards to... Reveal &top cards to... - + &Top of library... &Top of library... - + &Bottom of library... &Bottom of library... - + &Always reveal top card &Always reveal top card - + &Always look at top card &Always look at top card - + &Open deck in deck editor &Open deck in deck editor - + &Draw card &Draw card - + D&raw cards... D&raw cards... - + &Undo last draw &Undo last draw - + Shuffle Shuffle - + &Play top card &Play top card - + Play top card &face down Play top card &face down - + Put top card on &bottom Put top card on &bottom - + Move top card to grave&yard Move top card to grave&yard - + Move top card to e&xile Move top card to e&xile - + Move top cards to &graveyard... Move top cards to &graveyard... - + + Move top cards to graveyard face down... + Move top cards to graveyard face down... + + + Move top cards to &exile... Move top cards to &exile... - + + Move top cards to exile face down... + Move top cards to exile face down... + + + Put top cards on stack &until... Put top cards on stack &until... - + Shuffle top cards... Shuffle top cards... - + &Draw bottom card &Draw bottom card - + D&raw bottom cards... D&raw bottom cards... - + &Play bottom card &Play bottom card - + Play bottom card &face down Play bottom card &face down - + Move bottom card to grave&yard Move bottom card to grave&yard - + Move bottom card to e&xile Move bottom card to e&xile - + Move bottom cards to &graveyard... Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + Move bottom cards to graveyard face down... + + + Move bottom cards to &exile... Move bottom cards to &exile... - + + Move bottom cards to exile face down... + Move bottom cards to exile face down... + + + Put bottom card on &top Put bottom card on &top - + Shuffle bottom cards... Shuffle bottom cards... - - + + &All players &All players - + Reveal top cards of library Reveal top cards of library - + Number of cards: (max. %1) Number of cards: (max. %1) @@ -4756,18 +4780,8 @@ Will now login. Will now login. - - Number of players - Number of players - - - - Please enter the number of players. - Please enter the number of players. - - - - + + Player %1 Player %1 @@ -4870,8 +4884,8 @@ Will now login. - - + + Error Error @@ -5279,36 +5293,36 @@ Local version is %1, remote version is %2. Show/Hide - + New Version New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5317,29 +5331,29 @@ Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Yes - - + + No No - + Open settings Open settings - + New sets found New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5350,17 +5364,17 @@ Set codes: %1 Do you want to enable them? - + View sets View sets - + Welcome Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5369,65 +5383,65 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Information - + A card database update is already running. A card database update is already running. - + Unable to run the card database updater: Unable to run the card database updater: - + Card database update running. Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. Unknown error occurred. - + The card database updater exited with an error: %1 The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5438,55 +5452,55 @@ This is most likely not a problem, but this message might mean there is a new ve To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Load sets/cards - + Selected file cannot be found. Selected file cannot be found. - + You can only import XML databases at this time. You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Sets/cards failed to import. - - - + + + Reset Password Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. Activation request received, please check your email for an activation token. @@ -5537,7 +5551,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base Mana Base @@ -5691,590 +5705,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play from play - + from their graveyard from their graveyard - + from exile from exile - + from their hand from their hand - + the top card of %1's library the top card of %1's library - + the top card of their library the top card of their library - + from the top of %1's library from the top of %1's library - + from the top of their library from the top of their library - + the bottom card of %1's library the bottom card of %1's library - + the bottom card of their library the bottom card of their library - + from the bottom of %1's library from the bottom of %1's library - + from the bottom of their library from the bottom of their library - + from %1's library from %1's library - + from their library from their library - + from sideboard from sideboard - + from the stack from the stack - + from custom zone '%1' from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 attaches %2 to %3's %4. - + %1 has conceded the game. %1 has conceded the game. - + %1 has unconceded the game. %1 has unconceded the game. - + %1 has restored connection to the game. %1 has restored connection to the game. - + %1 has lost connection to the game. %1 has lost connection to the game. - + %1 points from their %2 to themselves. %1 points from their %2 to themselves. - + %1 points from their %2 to %3. %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. %1 creates a face down token. - + %1 creates token: %2%3. %1 creates token: %2%3. - + %1 has loaded a deck (%2). %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. %1 destroys %2. - + a card a card - + %1 gives %2 control over %3. %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 puts %2 into play%3. - + + %1 puts %2%3 into their graveyard face down. + %1 puts %2%3 into their graveyard face down. + + + %1 puts %2%3 into their graveyard. %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + %1 exiles %2%3 face down. + %1 exiles %2%3. %1 exiles %2%3. - + %1 moves %2%3 to their hand. %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 moves %2%3 to sideboard. - + + %1 plays %2%3 face down. + %1 plays %2%3 face down. + + + %1 plays %2%3. %1 plays %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + %1 moves %2%3 to custom zone '%4' face down. + + + %1 moves %2%3 to custom zone '%4'. %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 tries to draw from an empty library - + %1 draws %2 card(s). %1 draws %2 card.%1 draws %2 cards. - + %1 is looking at %2. %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 is looking at the %4 %3 card %2.%1 is looking at the %4 %3 cards %2. - + bottom bottom - + top top - + %1 turns %2 face-down. %1 turns %2 face-down. - + %1 turns %2 face-up. %1 turns %2 face-up. - + The game has been closed. The game has been closed. - + The game has started. The game has started. - + You are flooding the game. Please wait a couple of seconds. You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 has joined the game. - + %1 is now watching the game. %1 is now watching the game. - + You have been kicked out of the game. You have been kicked out of the game. - + %1 has left the game (%2). %1 has left the game (%2). - + %1 is not watching the game any more (%2). %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 shuffles their deck and draws a card.%1 shuffles their deck and draws a new hand of %2 cards. - + %1 shuffles their deck and draws a new hand. %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. You are watching a replay of game #%1. - + %1 is ready to start the game. %1 is ready to start the game. - + cards an unknown amount of cards cards - + %1 card(s) a card for singular, %1 cards for plural one card%1 cards - + %1 lends %2 to %3. %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 reveals %2 to %3. - + %1 reveals %2. %1 reveals %2. - + %1 randomly reveals %2%3 to %4. %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. %1 reveals %2%3 to %4. - + %1 reveals %2%3. %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 reversed turn order, now it's %2. - + reversed reversed - + normal normal - + Heads Heads - + Tails Tails - + %1 flipped a coin. It landed as %2. %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1's turn. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 places %2 "%3" counter on %4 (now %5).%1 places %2 "%3" counters on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 removes %2 "%3" counter from %4 (now %5).%1 removes %2 "%3" counters from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. %1 sets %2 to untap normally. - + %1 removes the PT of %2. %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 has locked their sideboard. - + %1 has unlocked their sideboard. %1 has unlocked their sideboard. - + %1 taps their permanents. %1 taps their permanents. - + %1 untaps their permanents. %1 untaps their permanents. - + %1 taps %2. %1 taps %2. - + %1 untaps %2. %1 untaps %2. - + %1 shuffles %2. %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 unattaches %2. - + %1 undoes their last draw. %1 undoes their last draw. - + %1 undoes their last draw (%2). %1 undoes their last draw (%2). @@ -6282,110 +6316,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 Word1 Word2 Word3 - + Add New Message Add New Message - + Edit Message Edit Message - + Remove Message Remove Message - + Add message Add message - - + + Message: Message: - + Edit message Edit message - + Chat settings Chat settings - + Custom alert words Custom alert words - + Enable chat mentions Enable chat mentions - + Enable mention completer Enable mention completer - + In-game message macros In-game message macros - + How to use in-game message macros How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users Ignore private messages sent by unregistered users - - + + Invert text color Invert text color - + Enable desktop notifications for private messages Enable desktop notifications for private messages - + Enable desktop notification for mentions Enable desktop notification for mentions - + Enable room message history on join Enable room message history on join - - + + (Color is hexadecimal) (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only Separate words with a space, alphanumeric characters only @@ -6398,32 +6432,37 @@ Cockatrice will now reload the card database. Move to - + &Top of library in random order &Top of library in random order - + X cards from the top of library... X cards from the top of library... - + &Bottom of library in random order &Bottom of library in random order - + + T&able + T&able + + + &Hand &Hand - + &Graveyard &Graveyard - + &Exile &Exile @@ -6565,57 +6604,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Untap step - + Upkeep step Upkeep step - + Draw step Draw step - + First main phase First main phase - + Beginning of combat step Beginning of combat step - + Declare attackers step Declare attackers step - + Declare blockers step Declare blockers step - + Combat damage step Combat damage step - + End of combat step End of combat step - + Second main phase Second main phase - + End of turn step End of turn step @@ -6632,134 +6671,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) Number of cards: (max. %1) - + View bottom cards of library View bottom cards of library - + Shuffle top cards of library Shuffle top cards of library - + Shuffle bottom cards of library Shuffle bottom cards of library - + Draw hand Draw hand - + 0 and lower are in comparison to current hand size 0 and lower are in comparison to current hand size - + Draw cards Draw cards + + + + + + grave + grave + - Move top cards to grave - Move top cards to grave + + + + exile + exile - - Move top cards to exile - Move top cards to exile + + Move top cards to %1 + Move top cards to %1 - - Move bottom cards to grave - Move bottom cards to grave + + Move bottom cards to %1 + Move bottom cards to %1 - - Move bottom cards to exile - Move bottom cards to exile - - - + Draw bottom cards Draw bottom cards - - + + C&reate another %1 token C&reate another %1 token - + Create tokens Create tokens - - + + Number: Number: - + Place card X cards from top of library Place card X cards from top of library - + Which position should this card be placed: Which position should this card be placed: - + (max. %1) (max. %1) - + Change power/toughness Change power/toughness - + Change stats to: Change stats to: - + Set annotation Set annotation - + Please enter the new annotation: Please enter the new annotation: - + Set counters Set counters @@ -6767,48 +6810,69 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" Player "%1" - + &Counters &Counters + + + PrintingDisabledInfoWidget - - S&ay - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + Enable printings again + Enable printings again PrintingSelector - + Display Navigation Buttons Display Navigation Buttons + + + Printing Selector + Printing Selector + PrintingSelectorCardOverlayWidget - + Preference Preference - + Pin Printing Pin Printing - + Unpin Printing Unpin Printing - + Show Related cards Show Related cards @@ -6857,17 +6921,25 @@ Cockatrice will now reload the card database. Release Date - - + + Descending Descending - + Ascending Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + Select a card to view its available printings + + PtMenu @@ -7006,6 +7078,45 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + Confirm Change + Confirm Change + QPlatformTheme @@ -7154,37 +7265,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile &Exile - + &View exile &View exile - + &Move exile to... &Move exile to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &Hand &Hand - + &Graveyard &Graveyard @@ -7227,6 +7338,14 @@ Cockatrice will now reload the card database. Games + + SayMenu + + + S&ay + S&ay + + SequenceEdit @@ -7291,53 +7410,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts Restore all default shortcuts - + Do you really want to restore all default shortcuts? Do you really want to restore all default shortcuts? - + Clear all default shortcuts Clear all default shortcuts - + Do you really want to clear all shortcuts? Do you really want to clear all shortcuts? - + Section: Section: - + Action: Action: - + Shortcut: Shortcut: - + How to set custom shortcuts How to set custom shortcuts - + Clear all shortcuts Clear all shortcuts - + Search by shortcut name Search by shortcut name @@ -7406,27 +7525,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Enable &sounds - + Current sounds theme: Current sounds theme: - + Test system sound engine Test system sound engine - + Sound settings Sound settings - + Master volume Master volume @@ -7642,59 +7761,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. Desc. - + + + AND + AND + + + + + Require ALL selected colors + Require ALL selected colors + + + + + Deck name... + Deck name... + + + + + Owner... + Owner... + + + + + Packages + Packages + + + + + Advanced Filters + Advanced Filters + + + + Bracket: + Bracket: + + + + + Any + Any + + + + + Contains card... + Contains card... + + + + + Commander... + Commander... + + + + + Tag... + Tag... + + + + + Deck Size + Deck Size + + + + Cards: + Cards: + + + + Asc. Asc. - - - Any Bracket - Any Bracket + + Sort by: + Sort by: - - Deck name contains... - Deck name contains... + + Filter by: + Filter by: - - Owner name contains... - Owner name contains... + + Display Settings + Display Settings - - Disabled - Disabled - - - + + Search Search - + + Formats Formats - - Min. # of Cards: - Min. # of Cards: - - - - Page: - Page: - - - + Archidekt: Archidekt: @@ -7702,60 +7885,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Card Info - + Deck Deck - + Filters Filters - + &View &View - + Card Database Card Database - + Printing Printing - - - - - + Visible Visible - - - - - + Floating Floating - + Reset layout Reset layout - + Deck: %1 Deck: %1 @@ -7763,61 +7938,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 Visual Deck: %1 - + &Visual Deck Editor &Visual Deck Editor - - + + Card Info Card Info - - + + Deck Deck - - + + Filters Filters - + &View &View - + Printing Printing - - - - + Visible Visible - - - - + Floating Floating - + Reset layout Reset layout @@ -7882,7 +8051,7 @@ Please check your shortcut settings! - + New folder New folder @@ -7964,18 +8133,18 @@ Please enter a name: Are you sure you want to delete the selected files? - + Delete remote decks Delete remote decks - + Are you sure you want to delete the selected decks? Are you sure you want to delete the selected decks? - + Name of new folder: Name of new folder: @@ -7993,12 +8162,12 @@ Please enter a name: Visual Deck Storage - + Error Error - + Could not open deck at %1 Could not open deck at %1 @@ -8047,197 +8216,191 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Game - - + + Player List Player List - - + + Card Info Card Info - - + + Messages Messages - - + + Replay Timeline Replay Timeline - + &Phases &Phases - + &Game &Game - + Next &phase Next &phase - + Next phase with &action Next phase with &action - + Next &turn Next &turn - + Reverse turn order Reverse turn order - + &Remove all local arrows &Remove all local arrows - + Rotate View Cl&ockwise Rotate View Cl&ockwise - + Rotate View Co&unterclockwise Rotate View Co&unterclockwise - + Game &information Game &information - + Un&concede Un&concede - - - + + + &Concede &Concede - + &Leave game &Leave game - + C&lose replay C&lose replay - + &Focus Chat &Focus Chat - + &Say: &Say: - + Selected cards Selected cards - + &View &View - - - - + Visible Visible - - - - + Floating Floating - + Reset layout Reset layout - + Concede Concede - + Are you sure you want to concede this game? Are you sure you want to concede this game? - + Unconcede Unconcede - + You have already conceded. Do you want to return to this game? You have already conceded. Do you want to return to this game? - + Leave game Leave game - + Are you sure you want to leave this game? Are you sure you want to leave this game? - + A player has joined game #%1 A player has joined game #%1 - + %1 has joined the game %1 has joined the game - + You have been kicked out of the game. You have been kicked out of the game. @@ -9340,142 +9503,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings General interface settings - + &Double-click cards to play them (instead of single-click) &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases Do not delete &arrows inside of subphases - + Close card view window when last card is removed Close card view window when last card is removed - + Auto focus search bar when card view window is opened Auto focus search bar when card view window is opened - + Annotate card text on tokens Annotate card text on tokens - + + Show selection counter during drag selection + Show selection counter during drag selection + + + + Show total selection counter + Show total selection counter + + + Use tear-off menus, allowing right click menus to persist on screen Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings Notifications settings - + Enable notifications in taskbar Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect Notify in the taskbar when users in your buddy list connect - + Animation settings Animation settings - + &Tap/untap animation &Tap/untap animation - + Deck editor/storage settings Deck editor/storage settings - + Open deck in new tab by default Open deck in new tab by default - + Use visual deck storage in game lobby Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: When adding a tag in the visual deck storage to a .txt deck: - + do nothing do nothing - + ask to convert to .cod ask to convert to .cod - + always convert to .cod always convert to .cod - + Default deck editor type Default deck editor type - + Classic Deck Editor Classic Deck Editor - + Visual Deck Editor Visual Deck Editor - + Replay settings Replay settings - + Buffer time for backwards skip via shortcut: Buffer time for backwards skip via shortcut: @@ -9539,24 +9712,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match - Mode: Exact Match + + Exact match + Exact match + + + + Includes + Includes - Mode: Includes - Mode: Includes + Include / Exclude + Mode: Includes + Include / Exclude - - Mode: Include/Exclude - Mode: Include/Exclude - - - - Filter mode (AND/OR/NOT conjunctions of filters) - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter + How selected and unselected colors are combined in the filter @@ -9582,25 +9756,108 @@ Please refrain from engaging in this activity or further actions may be taken ag Enter filename... + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + Sort by + + + + Filter by + Filter by + + + + Save and load filters + Save and load filters + + + + Filter by exact card name + Filter by exact card name + + + + Filter by card main-type + Filter by card main-type + + + + Filter by card sub-type + Filter by card sub-type + + + + Filter by set + Filter by set + + + + Filter by format legality + Filter by format legality + + + + Save/Load + Save/Load + + + + Name + Name + + + + Main Type + Main Type + + + + Sub Type + Sub Type + + + + Sets + Sets + + + + Formats + Formats + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + Show formats with at least: + + + + cards + cards + + + Do not display formats with less than this amount of cards in the database Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes @@ -9608,22 +9865,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + Show main types with at least: + + + + cards + cards + + + Do not display card main-types with less than this amount of cards in the database Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes @@ -9659,7 +9926,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filter to most recent sets @@ -9667,19 +9934,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... Search sets... - - + + Mode: Exact Match Mode: Exact Match - - + + Mode: Includes Mode: Includes @@ -9687,27 +9954,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... Search subtypes... - + + Show sub types with at least: + Show sub types with at least: + + + + cards + cards + + + Do not display card sub-types with less than this amount of cards in the database Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes @@ -9721,52 +9998,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual Visual - + Loading database ... Loading database ... - + Clear all filters Clear all filters - - Sort by: - Sort by: - - - - Filter by: - Filter by: - - - - Save and load filters - Save and load filters - - - - Filter by exact card name - Filter by exact card name - - - - Filter by card sub-type - Filter by card sub-type - - - - Filter by set - Filter by set - - - + Table Table @@ -9774,56 +10021,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: Group by: - + Change how cards are divided into categories/groups. Change how cards are divided into categories/groups. - + Sort by: Sort by: - + Click and drag to change the sort order within the groups Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + Add cards using the search bar or database tab to have them appear here + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Draw a new sample hand - + Sample hand size Sample hand size @@ -9831,17 +10086,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... Type a card name here for suggestions from the database... - + Quick search and add card Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9857,47 +10112,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders Show Folders - + Show Tag Filter Show Tag Filter - + + Show Color Identity + Show Color Identity + + + Show Tags On Deck Previews Show Tags On Deck Previews - + Show Banner Card Selection Option Show Banner Card Selection Option - + Draw unused Color Identities Draw unused Color Identities - + Unused Color Identities Opacity Unused Color Identities Opacity - + Deck tooltip: Deck tooltip: - + None None - + Filepath Filepath @@ -9998,133 +10258,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Move selected set to the top - + Move selected set up Move selected set up - + Move selected set down Move selected set down - + Move selected set to the bottom Move selected set to the bottom - + Search by set name, code, or type Search by set name, code, or type - + Default order Default order - + Restore original art priority order Restore original art priority order - + Enable all sets Enable all sets - + Disable all sets Disable all sets - + Enable selected set(s) Enable selected set(s) - + Disable selected set(s) Disable selected set(s) - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) - + Include cards rebalanced for Alchemy [requires restart] Include cards rebalanced for Alchemy [requires restart] - + Card Art Card Art - + How to use custom card art How to use custom card art - + Hints Hints - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead Use the current sorting as the set priority instead - + Sorts the set priority using the same column Sorts the set priority using the same column - + Manage sets Manage sets @@ -10132,72 +10392,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) Search by card name (or search expressions) - + Ungrouped Ungrouped - + Group by Type Group by Type - + Group by Mana Value Group by Mana Value - + Group by Color Group by Color - + Unsorted Unsorted - + Sort by Name Sort by Name - + Sort by Type Sort by Type - + Sort by Mana Cost Sort by Mana Cost - + Sort by Colors Sort by Colors - + Sort by P/T Sort by P/T - + Sort by Set Sort by Set - + shuffle when closing shuffle when closing - + pile view pile view @@ -10232,7 +10492,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor Deck Editor @@ -10313,7 +10573,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays Replays @@ -10465,7 +10725,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout Reset Layout @@ -10886,8 +11146,9 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap - Toggle Untap + Toggle Skip Untapping + Toggle Untap + Toggle Skip Untapping @@ -10906,98 +11167,102 @@ Please refrain from engaging in this activity or further actions may be taken ag + Play Card, Face Down + Play Card, Face Down + + + Attach Card... Attach Card... - + Unattach Card Unattach Card - + Clone Card Clone Card - + Create Token... Create Token... - + Create All Related Tokens Create All Related Tokens - + Create Another Token Create Another Token - + Set Annotation... Set Annotation... - + Select All Cards in Zone Select All Cards in Zone - + Select All Cards in Row Select All Cards in Row - + Select All Cards in Column Select All Cards in Column - + Reveal Selected Cards to All Players Reveal Selected Cards to All Players - - + + Bottom of Library Bottom of Library - + - - + + Exile Exile - + - + Graveyard Graveyard - + Hand Hand - - + + Top of Library Top of Library - - + Battlefield, Face Down Battlefield, Face Down @@ -11033,234 +11298,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack Stack - + Graveyard (Multiple) Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + Graveyard (Multiple), Face Down + + + + Exile (Multiple) Exile (Multiple) - + + + Exile (Multiple), Face Down + Exile (Multiple), Face Down + + + Stack Until Found Stack Until Found - + Draw Bottom Card Draw Bottom Card - + Draw Multiple Cards from Bottom... Draw Multiple Cards from Bottom... - + Draw Arrow... Draw Arrow... - + Remove Local Arrows Remove Local Arrows - + Leave Game Leave Game - + Concede Concede - + Roll Dice... Roll Dice... - + Shuffle Library Shuffle Library - + Shuffle Top Cards of Library Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) Mulligan (Same hand size) - + Mulligan (Hand size - 1) Mulligan (Hand size - 1) - + Draw a Card Draw a Card - + Draw Multiple Cards... Draw Multiple Cards... - + Undo Draw Undo Draw - + Always Reveal Top Card Always Reveal Top Card - + Always Look At Top Card Always Look At Top Card - + Sort Hand by Name Sort Hand by Name - + Sort Hand by Type Sort Hand by Type - + Sort Hand by Mana Value Sort Hand by Mana Value - + Reveal Hand to All Players Reveal Hand to All Players - + Reveal Random Card to All Players Reveal Random Card to All Players - + Rotate View Clockwise Rotate View Clockwise - + Rotate View Counterclockwise Rotate View Counterclockwise - + Unfocus Text Box Unfocus Text Box - + Focus Chat Focus Chat - + Clear Chat Clear Chat - + Refresh Refresh - + Skip Forward Skip Forward - + Skip Backward Skip Backward - + Skip Forward by a lot Skip Forward by a lot - + Skip Backward by a lot Skip Backward by a lot - + Play/Pause Play/Pause - + Toggle Fast Forward Toggle Fast Forward - + Home Home - + Visual Deck Storage Visual Deck Storage - + Deck Storage Deck Storage - + Server Server - + Account Account - + Administration Administration - + Logs Logs From d2732ac742cd7acf19719bb60ce5007ae3d8d91b Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 10 Apr 2026 13:42:05 +0200 Subject: [PATCH 33/60] fix prepare cards (#6792) --- oracle/src/oracleimporter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index d585842f6..b5d7b9856 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -360,13 +360,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList } // split cards are considered a single card, enqueue for later merging - if (layout == "split" || layout == "aftermath" || layout == "adventure") { + if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") { auto _faceName = getStringPropertyFromMap(card, "faceName"); SplitCardPart split(_faceName, text, properties, printingInfo); auto found_iter = splitCards.find(name + numProperty); if (found_iter == splitCards.end()) { splitCards.insert(name + numProperty, {{split}, name}); - } else if (layout == "adventure") { + } else if (layout == "adventure" || layout == "prepare") { found_iter->first.insert(0, split); } else { found_iter->first.append(split); From f9fb03b26b796bdfd1ac298110224598dc506cdc Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 10 Apr 2026 18:17:43 +0200 Subject: [PATCH 34/60] update all runners to use qt6.11 (#6794) * update all runners to use qt6.11 * use aqt version directly from repo --- .github/workflows/desktop-build.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 7bd84eb21..c93666640 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -270,7 +270,7 @@ jobs: override_target: 13 make_package: 1 package_suffix: "-macOS13_Intel" - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -284,7 +284,7 @@ jobs: type: Release make_package: 1 package_suffix: "-macOS14" - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -298,7 +298,7 @@ jobs: type: Release make_package: 1 package_suffix: "-macOS15" - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -310,7 +310,7 @@ jobs: soc: Apple xcode: "16.4" type: Debug - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -322,7 +322,7 @@ jobs: type: Release make_package: 1 package_suffix: "-Win10" - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: win64_msvc2022_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: "Visual Studio 17 2022" @@ -409,6 +409,8 @@ jobs: if: matrix.os == 'Windows' uses: jurplel/install-qt-action@v4 with: + # qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released + aqtsource: git+https://github.com/miurahr/aqtinstall.git version: ${{ steps.resolve_qt_version.outputs.version }} arch: ${{matrix.qt_arch}} modules: ${{matrix.qt_modules}} From 36aba81b1b668fcfafcbd4b0966e35d0b43dca26 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 10 Apr 2026 18:18:08 +0200 Subject: [PATCH 35/60] adds eternal to prioritisation list (#6793) --- oracle/src/oracleimporter.h | 1 + 1 file changed, 1 insertion(+) diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index e83958d73..5bf352594 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -22,6 +22,7 @@ const QMap setTypePriorities{ {"archenemy", CardSet::PriorityReprint}, {"arsenal", CardSet::PriorityReprint}, {"box", CardSet::PriorityReprint}, + {"eternal", CardSet::PriorityReprint}, {"from_the_vault", CardSet::PriorityReprint}, {"masterpiece", CardSet::PriorityReprint}, {"masters", CardSet::PriorityReprint}, From 29c1d7f3e4eada9981da383e80728df9dff0e5f0 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 11 Apr 2026 18:19:11 +0200 Subject: [PATCH 36/60] add timeout to job matrixes (#6797) --- .github/workflows/desktop-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index c93666640..653df4ba0 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -156,6 +156,7 @@ jobs: needs: configure runs-on: ubuntu-latest continue-on-error: ${{matrix.allow-failure == 'yes'}} + timeout-minutes: 70 env: NAME: ${{matrix.distro}}${{matrix.version}} CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache @@ -331,6 +332,7 @@ jobs: name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} needs: configure runs-on: ${{matrix.runner}} + timeout-minutes: 100 env: CCACHE_DIR: ${{github.workspace}}/.cache/ # Cache size over the entire repo is 10Gi: From f56b6723070fedbf3a31c63b195a341d61feff21 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 11 Apr 2026 18:20:35 +0200 Subject: [PATCH 37/60] CI: Allow failing of ccache deletion step (#6799) * Don't fail cache delete step * Use `continue-on-error` * remove leftover --- .github/workflows/desktop-build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 653df4ba0..8c3fd01de 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -208,9 +208,10 @@ jobs: --ccache "$CCACHE_SIZE" $NO_CLIENT .ci/name_build.sh - # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342. + # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - name: Delete remote compiler cache (ccache) if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit + continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} @@ -451,9 +452,10 @@ jobs: TARGET_MACOS_VERSION: ${{ matrix.override_target }} run: .ci/compile.sh --server --test --vcpkg - # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342. + # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - name: Delete remote compiler cache (ccache) if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit + continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} From e977f123ce47d393cc308b4575fe7444d8a6272c Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sat, 11 Apr 2026 19:18:12 +0200 Subject: [PATCH 38/60] add ccache eviction for files older than 7 days (#6795) * add ccache eviction for files older than 7 days also clean up some of the scripts to be more internally consistent * move name build into the docker container again * remove one extra empty line [skip ci] * allow canceling concurrent builds on master for both desktop and docker * add ccache eviction age to macos as well --- .ci/compile.sh | 29 +++++++++++++++++++++++- .ci/docker.sh | 29 +++++++++++++++++++----- .github/workflows/desktop-build.yml | 33 ++++++++++++++++++---------- .github/workflows/docker-release.yml | 5 +++++ 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/.ci/compile.sh b/.ci/compile.sh index 424527f96..7ebdd6e4e 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -10,9 +10,11 @@ # --test runs tests # --debug or --release sets the build type ie CMAKE_BUILD_TYPE # --ccache [] uses ccache and shows stats, optionally provide size +# --evict-ccache runs ccache eviction based on given age after build # --dir sets the name of the build dir, default is "build" +# --cmake-generator sets CMAKE_GENERATOR as used by cmake # --target-macos-version sets the min os version - only used for macOS builds -# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION +# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE CCACHE_EVICTION_AGE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION # (correspond to args: --debug/--release --install --package --suffix --server --test --ccache --dir ) # exitcode: 1 for failure, 3 for invalid arguments @@ -71,6 +73,15 @@ while [[ $# != 0 ]]; do shift fi ;; + '--evict-ccache') + shift + if [[ $# == 0 ]]; then + echo "::error file=$0::--evict-ccache expects an argument" + exit 3 + fi + CCACHE_EVICTION_AGE=$1 + shift + ;; '--vcpkg') USE_VCPKG=1 shift @@ -84,6 +95,15 @@ while [[ $# != 0 ]]; do BUILD_DIR="$1" shift ;; + '--cmake-generator') + shift + if [[ $# == 0 ]]; then + echo "::error file=$0::--cmake-generator expects an argument" + exit 3 + fi + export CMAKE_GENERATOR=$1 + shift + ;; '--target-macos-version') shift if [[ $# == 0 ]]; then @@ -251,9 +271,16 @@ cmake --build . "${buildflags[@]}" echo "::endgroup::" if [[ $USE_CCACHE ]]; then + if [[ $CCACHE_EVICTION_AGE ]]; then + echo "::group::evict ccache files older than $CCACHE_EVICTION_AGE" + ccache --evict-older-than "$CCACHE_EVICTION_AGE" + echo "::endgroup::" + fi echo "::group::Show ccache stats again" ccachestatsverbose echo "::endgroup::" +elif [[ $CCACHE_EVICTION_AGE ]]; then + echo "::error file=$0::ccache eviction is enabled while ccache is disabled!" fi if [[ $RUNNER_OS == macOS ]]; then diff --git a/.ci/docker.sh b/.ci/docker.sh index 911488ecf..46112daaa 100644 --- a/.ci/docker.sh +++ b/.ci/docker.sh @@ -3,17 +3,28 @@ # This script is to be used by the ci environment from the project root directory, do not use it from somewhere else. # Creates or loads docker images to use in compilation, creates RUN function to start compilation on the docker image. -# sets the name of the docker image, these correspond to directories in .ci +# +# usage: source