knowledgebase/journals/2023_12_25.md

11 KiB

  • Dialogo con #GPT-4 su mini linguaggio per filtri #Probo
    • Devo sviluppare un mini linguaggio di query all'interno di un progetto Go. Il linguaggio deve servire per filtrare degli elementi in base a tag, percorso di archiviazione, attributi, etc. In particolare si tratta di filtrare strutture di dati di questo tipo:
    • type Participant struct {
    • Firsfname string
    • Lastname string
    • Attributes map[string]string
    • }
    • type Quiz struct {
    • Question *Question
    • Answers []*Answer
    • Tags []*Tags
    • }
    • Un esempio di utilizzo del linguaggio potrebbe essere il seguente:
    • quizzes = tags (or ("tags1" "tags2")) , participants = attributes (or (eq ("class" "1 D LIN") ("class" "2 D LIN")))
    • In questo caso i quiz selezionati dovranno essere quelli contenenti il tag1 o il tag2 e i partecipanti dovranno essere quelli con attributo "class" uguale a "1 D LIN" oppure "2 D LIN".
    • Scrivi un prototipo di parser per questo linguaggio.
    • Sviluppare un parser per un linguaggio di query personalizzato può essere un compito impegnativo. Di seguito è riportato un esempio di come potrebbe essere realizzato un parser di base per il tuo linguaggio di query specifico. Tuttavia, va notato che questo è solo un esempio di base e potrebbe non coprire tutti i casi d'uso o errori di sintassi.
    • package main
      - import (
        "strings"
        "fmt"
      )
      - type Query struct {
        QuizzesFilter string
        ParticipantsFilter string
      }
      - func Parse(query string) (*Query, error) {
        parts := strings.Split(query, ",")
        if len(parts) != 2 {
            return nil, fmt.Errorf("query syntax error")
        }
      - quizzesFilter := strings.TrimSpace(parts[0])
        participantsFilter := strings.TrimSpace(parts[1])
      - if !strings.HasPrefix(quizzesFilter, "quizzes =") || !strings.HasPrefix(participantsFilter, "participants =") {
            return nil, fmt.Errorf("query syntax error")
        }
      - return &Query{
            QuizzesFilter: quizzesFilter[10:],
            ParticipantsFilter: participantsFilter[14:],
        }, nil
      }
      - func main() {
        query, err := Parse(`quizzes = tags (or ("tags1" "tags2")) , participants = attributes (or (eq ("class" "1 D LIN") ("class" "2 D LIN")))`)
        if err != nil {
            fmt.Println("Error:", err)
            return
        }
      - fmt.Println("Quizzes Filter:", query.QuizzesFilter)
        fmt.Println("Participants Filter:", query.ParticipantsFilter)
      }
      
    • Questo parser è molto semplice e si limita a dividere la query in due parti, una per i quiz e una per i partecipanti. Ogni parte viene poi assegnata alla struttura Query.
    • Per un parser più avanzato che può gestire errori di sintassi più complessi, potrebbe essere necessario utilizzare un parser generator come "go-yacc" o "peg", o scrivere un parser ricorsivo a discesa.
    • Scrivi un parser ricorsivo a discesa.
    • Un parser ricorsivo a discesa è un tipo di parser che inizia a leggere l'input dall'inizio e tenta di applicare le regole di produzione del linguaggio. Questo tipo di parser è chiamato "ricorsivo" perché richiama se stesso per gestire le sottoparti di una produzione.
    • Ecco un esempio di come potrebbe apparire un parser ricorsivo a discesa per il tuo linguaggio di query. Questo esempio è molto semplificato e non gestisce errori di sintassi o casi d'uso più complessi.
    • package main
      - import (
      	"fmt"
      	"strings"
      )
      - type Query struct {
      	QuizzesFilter     string
      	ParticipantsFilter string
      }
      - func Parse(input string) (*Query, error) {
      	return parseParts(strings.Split(input, ","))
      }
      - func parseParts(parts []string) (*Query, error) {
      	if len(parts) != 2 {
      return nil, fmt.Errorf("query syntax error")
      	}
      - quizzesFilter, err := parseFilter(parts[0])
      	if err != nil {
      return nil, err
      	}
      - participantsFilter, err := parseFilter(parts[1])
      	if err != nil {
      return nil, err
      	}
      - return &Query{
      QuizzesFilter:     quizzesFilter,
      ParticipantsFilter: participantsFilter,
      	}, nil
      }
      - func parseFilter(filter string) (string, error) {
      	parts := strings.SplitN(strings.TrimSpace(filter), "=", 2)
      	if len(parts) != 2 {
      return "", fmt.Errorf("filter syntax error")
      	}
      - return strings.TrimSpace(parts[1]), nil
      }
      - func main() {
      	query, err := Parse(`quizzes = tags (or ("tags1" "tags2")) , participants = attributes (or (eq ("class" "1 D LIN") ("class" "2 D LIN")))`)
      	if err != nil {
      fmt.Println("Error:", err)
      return
      	}
      - fmt.Println("Quizzes Filter:", query.QuizzesFilter)
      	fmt.Println("Participants Filter:", query.ParticipantsFilter)
      }
      
    • In questo esempio, il metodo Parse() divide l'input in parti utilizzando la virgola come delimitatore. Quindi, per ogni parte,il metodo parseFilter() viene chiamato per analizzare ulteriormente la parte. Questo metodo divide la parte in due utilizzando l'uguale come delimitatore e restituisce la seconda parte, che rappresenta il filtro.
    • Va notato che questo è un esempio molto semplificato di un parser ricorsivo a discesa. In un parser reale, ci sarebbero molte più regole di produzione per gestire tutti i possibili casi d'uso e errori di sintassi.
    • Inoltre, questo parser non fa nulla con i filtri una volta che li ha estratti. In un'applicazione reale, questi filtri sarebbero probabilmente usati per filtrare un insieme di dati.
    • Infine, questo parser non verifica che i filtri siano sintatticamente corretti. Ad esempio, accetterebbe un filtro come quizzes = tags (or ("tags1" "tags2")) , participants = attributes (eq ("class" "1 D LIN") ("class" "2 D LIN")) anche se manca una parentesi chiusa nel filtro dei partecipanti. In un parser reale, ci sarebbe una logica aggiuntiva per verificare che i filtri siano ben formati.
    • Conosci il package "participle"?
    • Participle è un packageaggio di query personalizzato come il tuo.
    • Ecco un esempio di come potresti usare Participle per creare un parser per il tuo linguaggio di query:
    • package main
      - import (
        "fmt"
        "github.com/alecthomas/participle"
        "github.com/alecthomas/participle/lexer"
      )
      - type Query struct {
        Quizzes []*Quiz `@@*`
        Participants []*Participant `@@*`
      }
      - type Quiz struct {
        Tags []string `"tags" "(" @@* ")"`
      }
      - type Participant struct {
        Attributes []Attribute `"attributes" "(" @@* ")"`
      }
      - type Attribute struct {
        Key string `@String`
        Value string `@String`
      }
      - var (
        parser = participle.MustBuild(&Query{},
            participle.Lexer(lexer.MustSimple([]lexer.Rule{
                {"String", `"[\w\s]+"`, nil},
                {"Whitespace", `\s+`, nil},
                {"Punct", `[-[!@#$%^&*()+_={};':",.<>?\/\]|]`, nil},
            })),
            participle.Elide("Whitespace"),
        )
      )
      - func main() {
        query := &Query{}
        err := parser.ParseString(`quizzes = tags ("tags1" "tags2") , participants = attributes ("class" "1 D LIN") ("class" "2 D LIN")`, query)
        if err != nil {
            fmt.Printf("Error: %s\n", err)
            return
        }
      - fmt.Printf("Parsed: %+v\n", query)
      }
      
    • In questo esempio, le strutture di Go vengono utilizzate per definire la grammatica del linguaggio di query. Il metodo participle.MustBuild() viene utilizzato per creare un parser da questa grammatica.
    • Il metodo parser.ParseString() viene quindi utilizzato per analizzare una stringa di query e popolare la struttura Query.
    • Si noti che questo è un esempio semplificato e potrebbe essere necessario adattarlo per gestire tutti i casi d'uso del tuo linguaggio di query. Ad esempio, potrebbe essere necessario gestire operatori logici come "and" e "or", o gestire errori di sintassi più complessi.
    • Prevedi l'utilizzo degli operatori booleani e anche della negazione tramite prefisso "!". Produci anche i test.
    • del tuo linguaggio di query. Di seguito è riportato un esempio di come potrebbe essere fatto:
    • package main
      - import (
      	"fmt"
      	"github.com/alecthomas/participle"
      	"github.com/alecthomas/participle/lexer"
      )
      - type Query struct {
      	Quizzes      []*Quiz      `@@*`
      	Participants []*Participant `@@*`
      }
      - type Quiz struct {
      	Tags []string `"tags" "(" @@* ")"`
      }
      - type Participant struct {
      	Attributes []Attribute `"attributes" "(" @@* ")"`
      }
      - type Attribute struct {
      	Key   string `@String`
      	Value string `@String`
      }
      - type Operator struct {
      	Not  *string  `@"!"?`
      	Type *string `@("and"|"or")`
      }
      - var (
      	parser = participle.MustBuild(&Query{},
      participle.Lexer(lexer.MustSimple([]lexer.Rule{
      	{"String", `"[\w\s]+"`, nil},
      	{"Whitespace", `\s+`, nil},
      	{"Punct", `[-[!@#$%^&*()+_={};':",.<>?\/\]|]`, nil},
      })),
      participle.Elide("Whitespace"),
      	)
      )
      - func main() {
      	query := &Query{}
      	err := parser.ParseString(`quizzes = tags ("tags1" "tags2") , participants = attributes ("class" "1 D LIN") ("class" "2 D LIN")`, query)
      	if err != nil {
      fmt.Printf("Error: %s\n", err)
      return
      	}
      - fmt.Printf("Parsed: %+v\n", query)
      }
      
    • Nel codice sopra, ho aggiunto un nuovo tipo Operator che può rappresentare un operatore booleano ("and" o "or") o una negazione ("!"). Questo tipo viene utilizzato in Quiz e Participant per rappresentare gli operatori tra i tag e gli attributi.
    • Per quanto riguarda i test, potresti scrivere dei test di unità che verificano che il tuo parser gestisce correttamente vari casi d'uso e errori di sintassi. Ecco un esempio di come potrebbe apparire un test di base:
    • package main
      - import (
      	"testing"
      )
      - func TestParse(t *testing.T) {
      	tests := []struct {
      input string
      want  *Query
      	}{
      {
      	input: `quizzes = tags ("tags1" "tags2") , participants = attributes ("class" "1 D LIN") ("class" "2 D LIN")`,
      	want: &Query{
      		Quizzes: []*Quiz{
      			{Tags: []string{"tags1", "tags2"}},
      		},
      		Participants: []*Participant{
      			{Attributes: []Attribute{{Key: "class", Value: "1 D LIN"}, {Key: "class", Value: "2 D LIN"}}},
      		},
      	},
      },
      // Add more test cases here.
      	}
      - for _, test := range tests {
      got, err := Parse(test.input)
      if err != nil {
      	t.Fatalf("Parse(%q) returned error: %s", test.input, err)
      }
      if !reflect.DeepEqual(got, test.want) {
      	t.Errorf("Parse(%q) = %+v, want %+v", test.input, got, test.want)
      }
      	}
      }
      
    • Questo test verifica che il metodo Parse() restituisce la struttura Query corretta per un input di test. Puoi aggiungere più casi di test per verificare che il tuo parser gestisca correttamente vari casi d'uso e errori di sintassi.
  • 14:07 quick capture: Zotero for Android available for beta tests
  • 19:18 quick capture: GitHub - antonmedv/walk: Terminal file manager
  • 21:05 quick capture: GitHub - imartinez/privateGPT: Interact with your documents using the power of GPT, 100% privately, no data leaks
  • 21:18 quick capture: PrivateGPT
  • 21:31 quick capture: GitHub - tidwall/gjson: Get JSON values quickly - JSON parser for Go #json #Probo