Типы Go для грамматики BNF

Я работаю над книгой по созданию интерпретаторов и в качестве личного проекта я решил реализовать JLox на Go вместо Java, главным образом для изучения Go. В настоящее время я застрял в синтаксическом анализаторе и не знаю, как использовать систему типов для построения грамматики. Есть некоторые подсказки здесь но я хочу использовать типы а-ля Haskell или Rust (возможно я ошибаюсь) .

Пока что это то, что у меня есть:

package ast

import (
    "golox/lexer"
)

type Stream interface {
    Peek() lexer.Token
    Next() lexer.Token
}
type TokenStream []lexer.Token

func (s *TokenStream) Peek() lexer.Token {
    return (*s)[0]
}

func (s *TokenStream) Next() lexer.Token {
    result := (*s)[0]
    *s = (*s)[1:]
    return result
}

type NUMBER struct {value float32}
type STRING struct {value string}
type NULL struct {value any}
type BOOL struct {value bool}

func (v NUMBER) Value() float32 {
    return v.value
}

func (v STRING) Value() string {
    return v.value
}

func (v BOOL) Value() bool {
    return v.value
}

func (v NULL) Value() {
}

type Literal interface {
    NUMBER | STRING | BOOL | NULL
}

type Expr interface {Literal | Unary | Binary | Grouping}

type Grouping[E Expr] struct {
    right lexer.Token
    left lexer.Token
    expr *E
}

type Unary[E Expr] struct {
    operator lexer.Token
    expr *E
}

type Binary[E Expr] struct {
    operator lexer.Token
    left *E
    right *E
}

func ParseType(s Stream) (err error) {
    switch p:=s.Peek(); p {

    }

    return
}

Мое намерение состоит в том, чтобы ввести проверку потока токенов, а затем объединить эти токены в виде выражений разных типов. Мой главный вопрос касается типов, которые я определил, поскольку компилятор жалуется, что мои определения недействительны (я думал, что предоставления указателя в качестве поля для рекурсивного типа будет достаточно, но, увы, это не так). Любое понимание ценится! Я знаю, что, вероятно, существует реализация этого языка, но я хотел бы понять эту проблему, прежде чем проверять чужой ответ!

🤔 А знаете ли вы, что...
Go предоставляет богатую стандартную библиотеку.


51
1

Ответ:

Решено

Ошибка компиляции — invalid recursive type Expr, и это связано с тем, что вы объявляете Expr как набор типов, тип может быть одним из объединений (Литеральный | Унарный | Двоичный | Группировка). Компилятор пытается разрешить E во время компиляции на основе ограничения типа Expr, а также потому, что Expr содержит Grouping, что создает рекурсивную ссылку на тип, что приводит к ошибке.

Я думал, что предоставления указателя в качестве поля для рекурсивного типа будет достаточно, но, увы, это не так.

Вы можете задаться вопросом, почему это работает

type A struct {
    a *A
}

это потому, что компилятор точно знает, что такое *A (указатель на A), если не задействованы дженерики, тогда как ограничения рекурсивного типа недействительны, даже если вы используете указатель

В другом языке, таком как Rust, мы можем определить тип перечисления для описания всех возможных выражений:

#[derive(PartialEq, Clone, Debug)]
pub enum Literal {
    Bool(bool),
    Number(String),
    String(String),
    Null,
}

#[derive(Debug, PartialEq)]
pub enum Expression {
    Litera(Literal),
    Grouping(Box<Expression>),
    Unary(Token, Box<Expression>),
    Binary(Box<Expression>, Token, Box<Expression>),
}

Но поскольку в golang нет перечислимого типа, идиоматический способ его реализации — использование интерфейса.

var (
    _ Expr = new(Literal)
    _ Expr = new(Grouping)
    _ Expr = new(Unary)
    _ Expr = new(Binary)
)

type Expr interface {
    Evaluate() string
}

type Literal struct {
    Value string
}

func (l *Literal) Evaluate() string {
    return l.Value
}

type Grouping struct {
    Expr Expr
}

func (g *Grouping) Evaluate() string {
    if g.Expr == nil {
        return ""
    } else {
        return fmt.Sprintf("(group %s)", g.Expr.Evaluate())
    }
}

type Unary struct {
    Token *Token
    Right Expr
}

func (u *Unary) Evaluate() string {
    if u.Right == nil {
        return ""
    } else {
        token := ""
        if u.Token != nil {
            token = u.Token.Data
        }
        return fmt.Sprintf("(%s %s)", token, u.Right.Evaluate())
    }
}

type Binary struct {
    Left  Expr
    Token *Token
    Right Expr
}

type A struct {
    a *A
}

func (b *Binary) Evaluate() string {
    if b.Left == nil || b.Right == nil || b.Token == nil {
        return ""
    } else {
        return fmt.Sprintf("(%s %s %s)", b.Token.Data, b.Left.Evaluate(), b.Right.Evaluate())
    }
}