Go가 반드시 EBNF를 따르는 것은 아니다
이 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
의 퍼스LiteralType
struct
는 각각 제거struct{ v int }
해야 하고StructType
foo
는 TypeName
해야 한다.이 같은 코드를 본 AST
{1}
가 그렇게 되지 않았기 때문이다[2].포함
LiteralValue
foo
포함
struct
IfStmt
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에서SimpleStmt
와 parseSimpleStmt
는 분리되어 있지만 실제 분석기는 둘 다SimpleStmt
함수로 해석된다.EBNF와 Perzer, 문제 코드를 한층 더 비교해 보면
Expression
해석parseSimpleStmt 함수에 도달할 수 있다.이 함수PrimaryExpr에서 parsePrimaryExpr
x := p.parseOperand(lhs)
를 얻은 후 다음 토큰이 어떤 상황에 따라 분류 처리되고Operand
이후에 오는 토큰은 당연히 foo
이기 때문에 진입foo
의 조건이다.그리고 마침내 문제의 근본 원인1510행에 이르렀다.if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) {
이것{
은 token.LBRACE
의 물건이기 때문에 함수의 반환값token.LBRACE
만 판단했으면 좋겠다고 생각합니다.하지만 다음은 무엇LiteralType
일까.첫 번째isLiteralType
는 true
또는 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}
모두 찾을 수 있기 때문이다.지우고 나서LiteralValue
야LabeledStmt
[4]인 줄 알았다.Block
in {}
{
v:
1
}
이 글의'TypeName
을 포함한LabeledStmt
'그림을 다시 한 번 보면Block
은 struct
를 알고 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
, 즉 foo
면TypeName
이 압축{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.그가 쓴 바와 같이 해결 방법으로는 괄호로 묶어야 한다.↩︎
Reference
이 문제에 관하여(Go가 반드시 EBNF를 따르는 것은 아니다), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/koya_iwamura/articles/0558aa5bf035aa텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)