Go가 반드시 EBNF를 따르는 것은 아니다

8686 단어 Gotech
Go는 내가 좋아하는 언어 중 하나로 평소 즐겨 쓰는데 며칠 전 트위터에서 다음과 같은 부자연스러운 행동을 봤다.
이 Tweet의 루틴을 읽고 If문의 조건식struct(anonymous struct)을 초기화한 상태에서 컴파일에 성공했지만 foo(defined type)을 초기화한 경우 컴파일에 실패했습니다.
type foo struct { v int }

if struct{ v int }{1}.v == 1 {} // コンパイル成功
if foo{1}.v == 1 {} // コンパイル失敗
따라서 Go의 언어 규범The Go Programming Language Specification(Version of Jan 14, 2020)을 참조하면 해당 EBNF(Extended Backus-Naur Form)는
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
, 조건식에 분호가 없는 부분IfStmt... == 1으로 해석[1]했다.추가 트레이스Expression
Expression
→ UnaryExpr
→ PrimaryExpr
→ Operand
→ Literal
→ CompositeLit
→ LiteralType LiteralValue
이 되고 아래Expression의 퍼스LiteralTypestruct는 각각 제거struct{ v int }해야 하고StructTypefooTypeName해야 한다.
이 같은 코드를 본 AST{1}가 그렇게 되지 않았기 때문이다[2].

  • 포함LiteralValuefooast-struct

  • 포함structIfStmtast-foo
  • 우선 의도대로 세척foo을 포함한IfStmt 측면에서 살펴보자.AST를 통해 알 수 있듯이 조건식은 struct 결합IfStmt 형식으로 세척됩니다.아래 층에는 ==의 왼쪽Cond : *ast.BinaryExpr (Op: ==)과 오른쪽==이 있다.X : *ast.SelectorExpr 더 깊이 파헤치면 코드와 대응할 수 있다.
    한편, Y : *ast.BasicLit (Kind: INT)가 포함된 X는 어떻습니까?AST를 보면 조건식foo이 아니라 IfStmt==입니다.
    왜 이러는지 알아야 한다. 파세 자신을 볼 수밖에 없다.다행히 Go의 페르즈는 Go로 쓰여져 있어 인내심을 가지고 읽으면 언젠가는 이 수수께끼를 풀 수 있을 것이다.그럼 한번 봅시다.foo의 서비스 직원은 서면으로 Cond : *ast.Ident(Name:foo) 함수에 의해 책임을 진다.특히 If문의 초기화식과 조건식을 한데 모아IfStmt 함수 해석했다.해석기를 계속 읽으면 문제 코드에 번호가 없으면 1875행에서 parseIfStmt 해석parseIfHeader 함수까지.실제로 EBNF에서SimpleStmtparseSimpleStmt는 분리되어 있지만 실제 분석기는 둘 다SimpleStmt 함수로 해석된다.
    EBNF와 Perzer, 문제 코드를 한층 더 비교해 보면 Expression 해석parseSimpleStmt 함수에 도달할 수 있다.이 함수PrimaryExpr에서 parsePrimaryExprx := p.parseOperand(lhs)를 얻은 후 다음 토큰이 어떤 상황에 따라 분류 처리되고Operand 이후에 오는 토큰은 당연히 foo이기 때문에 진입foo의 조건이다.그리고 마침내 문제의 근본 원인1510행에 이르렀다.
    if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) {
    
    이것{token.LBRACE의 물건이기 때문에 함수의 반환값token.LBRACE만 판단했으면 좋겠다고 생각합니다.하지만 다음은 무엇LiteralType일까.첫 번째isLiteralTypetrue 또는 p.exprLev >= 0 || !isTypeName(x)가 0보다 크면 필사는 표현식(expression)을 처리하고 있음을 나타낸다.두 번째p.exprLev >= 0는 문자와 같이 현재 처리 중인 노드가 아니라고 판단한다true.
    왜 이런 조건을 붙였습니까?GiitHub에서 제출된 역사는 2009년으로 거슬러 올라갔지만 이유를 명확하게 설명하는 제출을 찾지 못했다.그래서 이 조건을 부가한 것은 고(Go)가 포스 처리를 간소화하는 디자인 사상을 가진 언어이기 때문이다[3].
    예를 들어 제거p.exprLev할 때!isTypeName(x)가 변수 이름TypeName인지 사용자 정의 유형if foo{v: 1} {}인지 제거foo할 때 판단할 수 없다.그것은 identifier단일면TypeName{v: 1}in{v: 1} 모두 찾을 수 있기 때문이다.지우고 나서LiteralValueLabeledStmt[4]인 줄 알았다.
  • Block in {}
  • {
    v:
        1
    }
    
    이 글의'TypeName을 포함한LabeledStmt'그림을 다시 한 번 보면Blockstruct를 알고 IfStmt의 형제 노드{}의 아래 층으로 돌아가 EBNF를 결정해야 하기 때문에 처리가 복잡해졌다[5]를 쉽게 상상할 수 있다.
    위에서 말한 바와 같이 Block의 정의는
    LiteralType   = StructType | ArrayType | "[" "..." "]" ElementType |
                    SliceType | MapType | TypeName .
    
    , 현재 취득Block이 불가능해 EBNF를 만족시킬 수 없다.또 이번에 CompositeLit의 예를 봤더니 다른 control structureLiteralType도 EBNF[6]를 만족시키지 못했다.
    각주
    이 글에서 말하는 퍼스, 필사는 문법 해석, 문법 분석기를 가리킨다.따라서 뜻을 해석할 때 오류가 발생하더라도 EBNF의 문법이 정확한지 여부에 따라 해석이 성공했는지 판단하기만 하면 된다.↩︎
    시각 형상 사용GoAst Viewer🙏 ↩︎
    Why are declarations backwards? - Go Frequently Asked Questions (FAQ) ↩︎
    @dqneo.여러 파일이 존재하는 경우 다른 파일TypeName에 있을 수 있기 때문에 이 시점에서는 여부IfStmt를 판별할 수 없다.파일마다 아스트의 처리와 유형 검사를 병합해야 이 문장의 해석과 문법 해석을 알 수 있다.↩︎
    SwitchStmt를 제거한 토큰은ForStmt, 즉 fooTypeName이 압축{v: 1}으로 압축되기 때문에 문제의 코드가 퍼스에서 성공할 수 있다는 의견도 있다.↩︎
    @dqneo.spec에서는 Aparsing ambiguity arises when a composite literalsing the TypName form of the Literal Typapearsanoperand the open과 the opening bracceof the blocof blocoff의 "if", "for", or "switch"statement, and the comosite literalisnot enclossed in parentheses, requabreases rackets.In this rare case, the opening brace of the literal is erroneously parsed as the one introducing the block of statements. To resolve the ambiguity, the composite literal must appear within parentheses.그가 쓴 바와 같이 해결 방법으로는 괄호로 묶어야 한다.↩︎

    좋은 웹페이지 즐겨찾기