Golang

Golang 입문기 - 문법 1

kahnco 2023. 10. 24. 04:08

서두

 이전 게시글에서, Golang이라는 프로그래밍 언어의 의의, 생겨난 목적 등등을 확인해보았다. 이번 게시글부터는 Golang에서 사용하는 문법과 다양한 언어적 특징을 알아보겠다. 해당 게시글은 https://go.dev/ref/spec 해당 페이지를 참조해서 진행하였다.

 


소개

 해당 버전은 pre-Go1.18 이후의 버전으로 genric이 추가된 버전을 타케팅한다.

 

 Golang은 시스템 프로그래밍을 염두에 두고 설계된 범용 언어이다. 이는 강력한 타입형 언어이고, GC를 지원하며 동시 프로그래밍을 명시적으로 지원한다. Golang으로 만들어진 프로그램들은 종속성을 효율적으로 관리할 수 있는 특징을 가진 패키지들로 구성된다.

 

 Golang의 문법적 구문은 간결하고 구문 분석이 간단하므로 통합 개발 환경과 같은 자동 도구를 통해 쉽게 분석할 수 있다.

 

 

 


표기법 (Notation)

 Golang의 표기법은 EBNF (Extended Backus-Naur Form)을 약간 변형한 WSN (Wirth Syntax Notation)을 채택하고 있다.

Syntax      = { Production } .
Production  = production_name "=" [ Expression ] "." .
Expression  = Term { "|" Term } .
Term        = Factor { Factor } .
Factor      = production_name | token [ "…" token ] | Group | Option | Repetition .
Group       = "(" Expression ")" .
Option      = "[" Expression "]" .
Repetition  = "{" Expression "}" .
● PL = Syntax(언어 구문) + 언어 의미

● Syntax란, 언어 구문으로써 PL의 구조를 의미한다.

● 언어 문맥에서 Produce란, BNF 식에서 Non-Terminal에서 Terminal로 해석하는 방향을 뜻한다. (좌에서 우측으로 해석하는 방향) BNF 문법에서 특정 형태 문장의 문법 부합 여부를 조사하기 위해 Non-Terminal부터 Produce를 수행하여, 해당 문장의 파스 트리 존재 여부를 리턴한다.

● Production Rule (생성 규칙): 언어에서 문장을 생성하는 규칙으로써 PL 문장은 문법에 의거하여 생성되어야 한다. 생성 규칙에서 왼쪽은 정의될 대상 Object, 오른쪽에는 Object에 대한 정의 내용이 위치하게 된다.

● Productions는 Terms와 다음 명령어들로 이뤄어진 표현식으로써, 표기된 순서대로 우선순위가 높아진다.
    - | alternation
    - () grouping
    - [] option (0 or 1 times)
    - {} repetition (0 to n times)

● 소문자로 된 Production은 어휘 토큰을 식별하는 데에 사용된다. 또한, Non-terminal은 CamelCase로 쓰인다. (CamelCase의 경우, 다른 패키지에서 변수 또는 함수를 사용하려는 경우 CamelCase를 사용해야 하지만, 그렇지 않은 경우는 camelCase를 안전하게 사용할 수 있다)

● a ... b 표현식은 a 부터 b 까지의 문자 집합을 나타낸다. 가로 줄임표 (...) 는 더 이상 지정되지 않은 다양한 열거형이나 코드 조각을 비공식적으로 표시하기 위해 사용된다. 이 가로 줄임표 (...) 는 Golang의 토큰이 아니다.

 


소스 코드 표기 (Source Code Representation)

  • Golang의 소스 코드는 UTF-8 으로 인코딩된 유니코드 텍스트이다. 텍스트는 정규화되지 않으므로 단일 악센트 코드 포인트는 악센트와 문자를 결합하여 생성된 동일한 문자와 구별된다. 이는 두 개의 코드 포인트로 처리된다.
  • 각 코드 포인트는 서로 다르기 때문에, 대문자와 소문자는 서로 다른 문자로 인식된다.
  • 구현 제한: 다른 도구와의 호환성을 위해서 컴파일러는 소스 텍스트에서 NUL 문자 (U+0000)를 허용하지 않을 수 있다.
  • 구현 제한: 다른 도구와의 호환성을 위해 컴파일러는 UTF-8 으로 인코딩된 바이트 순서 표시 (U+FEFF)가 소스 텍스트의 첫 번째 유니코드 코드 포인트인 경우 이를 무시할 수 있다. 바이트 순서 표시는 소스의 다른 곳에서는 허용되지 않을 수도 있다.

 


글자 (Characters)

 다음 용어는 Golang 언어에서 허용되는 유니코드 문자의 범주를 나타내는 데 사용된다.

newline        = /* the Unicode code point U+000A */ .
unicode_char   = /* an arbitrary Unicode code point except newline */ .
unicode_letter = /* a Unicode code point categorized as "Letter" */ .
unicode_digit  = /* a Unicode code point categorized as "Number, decimal digit" */ .
● newline: 개행 문자는 U+000A로 표시되는 유니코드의 특정 문자를 나타낸다. Go를 포함한 대부분의 프로그래밍 언어에서 이는 텍스트 줄의 끝과 새 줄의 시작을 나타내는 데 사용되는 줄 바꿈 문자 (LF)에 해당한다.
● unicode_char: unicode_char는 개행 문자 (U+000A)를 제외한 모든 임의의 유니코드 코드 포인트를 포함한다. 즉, 개행 문자를 제외한 문자, 숫자, 기호 및 특수 문자를 포함하는 유니코드 표준의 모든 문자를 나타낸다.
● unicode_letter: unicode_letter는 특히 유니코드 표준에 따라 "Letter"로 분류되는 유니코드 코드 포인트를 나타낸다. 유니코드 표준에서 문자는 다양한 클래스로 분류되며 "Letter"에는 전 세계 다양한 Writing System의 모든 문자가 포함된다. 이 범주에는 대문자와 소문자가 모두 포함된다.
● unicode_digit: unicode_digit는 "숫자, 십진수"로 분류된 유니코드 코드 포인트를 나타낸다. 유니코드에서는 숫자로 구분되며, "숫자, 십진수"에는 0부터 9까지의 숫자가 포함된다

 유니코드 표준 8.0의 섹션 4.5 "일반 범주" 에서는 문자 범주 집합을 정의한다. Golang 언어는 문자 범주 Lu, Ll, Lt, Lm 또는 Lo에 있는 모든 문자를 유니코드 문자로 처리하고 숫자 범주 Nd에 있는 문자를 유니코드 숫자로 처리한다.

 


문자와 숫자 (Letters and digits)

letter        = unicode_letter | "_" .
decimal_digit = "0" … "9" .
binary_digit  = "0" | "1" .
octal_digit   = "0" … "7" .
hex_digit     = "0" … "9" | "A" … "F" | "a" … "f" .
  • 언더바 (_) 는 소문자로 분류된다.

 


어휘 요소 (Lexical Elements)

 

주석 (Comments)

 

 주석은 프로그램의 설명서 역할을 한다. Golang에서 사용되는 주석은 두 가지 형태가 있다.

  1. 줄 주석 (Line Comments)은 // 문자 시퀀스로 시작하고 줄 끝에서 중지된다.
  2. 일반 주석 (General Comments)은 문자 시퀀스 /* 으로 시작하고 그 다음에 등장하는 문자 시퀀스 */ 에서 중지된다.

 주석은 룬 (Rune: 유니코드 코드 포인트를 나타내는 데이터 유형), 문자열 내부 혹은 주석 내부에서 중첩으로 시작할 수 없다. 개행 문자가 포함되지 않은 일반 주석은 공백처럼 작동한다. 일반 주석을 제외한 다른 주석은 개행 문자처럼 동작한다.

 

토큰 (Tokens)

 

 토큰은 Go 언어의 어휘를 구성한다. 식별자, 키워드, 연산자, 구두점, 리터럴의 네 가지 클래스가 존재한다. 공백(U+0020), 가로 탭(U+0009), 캐리지 리턴(U+000D) 및 줄 바꿈(U+000A)으로 구성된 공백은 단일로 결합되는 토큰을 분리하는 경우를 제외하고는 무시된다. 또한, 줄 바꿈이나 파일 끝에는 세미콜론이 삽입될 수도 있다. 입력된 값을 토큰으로 나누는 동안, 다음 토큰은 유요한 토큰을 구성하는 가장 긴 문자 시퀀스이다.

 

세미콜론 (Semicolons)

 

 공식 구문에서 세미콜론은 다수의 프로그램에서의 종결자로 사용된다. 하지만 Go 프로그램은 다음 두 가지 규칙을 사용하여 대부분의 세미콜론을 생략할 수 있다.

 

  1. 입력된 값이 토큰으로 분할될 때, 해당 토큰이 다음과 같은 경우 행의 최종 토큰 바로 뒤에 세미콜론이 자동으로 토큰 스트림에 삽입된다.
    • 식별자
    • 정수, 부동 소수점, 허수, 룬 (Rune) 또는 문자열 리터럴
    • break, continue, fallthrough 혹은 return 키워드 중 하나
    • 연산자 및 구두점 (++, --, ), ], }) 중 하나
  2. 복잡한 명령문을 줄 하나로 처리하려면 닫는 ")" 혹은 "}" 앞에 세미콜론을 생략할 수 있다.

 

식별자 (Identifiers)

 

 식별자는 변수 및 타입과 같은 프로그램 엔티티의 이름을 지정한다. 식별자는 하나 이상의 문자와 숫자로 구성된 시퀀스이다. 식별자의 첫번째 문자는 문자여야 한다.

 

identifier = letter { letter | unicode_digit } .
 a
_x9
ThisVariableIsExported
αβ

 

키워드 (Keywords)

 

 다음 키워드들은 예약어들이고, 식별자로써 사용될 수 없다.

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

 

연산자 및 구두점 (Operators and Punctuation)

 

 다음 문자 시퀀스는 연산자와 구두점을 나타낸다.

+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^=          ~

 

정수 리터럴 (Integer Literals)

 

 정수 리터럴은 정수 상수를 나타내는 일련의 숫자이다. 선택적 접두사는 10진수가 아닌 타입에만 적용된다. 2진수는 0b 또는 0B, 8진수는 0, 0o 또는 0O, 16진수는 0x 또는 0X이다. 단일 0은 10진수 0으로 간주된다. 16진수 리터럴에서 문자 a~f 및 A~F는 10~15 값을 나타낸다.

 

 가독성을 위해 기본 접두사 뒤나 연속 숫자 사이에 언더 바가 나타날 수 있다. 이러한 언더 바는 리터럴 값을 변경하지 않는다.

int_lit        = decimal_lit | binary_lit | octal_lit | hex_lit .
decimal_lit    = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] .
binary_lit     = "0" ( "b" | "B" ) [ "_" ] binary_digits .
octal_lit      = "0" [ "o" | "O" ] [ "_" ] octal_digits .
hex_lit        = "0" ( "x" | "X" ) [ "_" ] hex_digits .

decimal_digits = decimal_digit { [ "_" ] decimal_digit } .
binary_digits  = binary_digit { [ "_" ] binary_digit } .
octal_digits   = octal_digit { [ "_" ] octal_digit } .
hex_digits     = hex_digit { [ "_" ] hex_digit } .
42
4_2
0600
0_600
0o600
0O600       // 두 번째에 있는 문자는 대문자 O
0xBadFace
0xBad_Face
0x_67_7a_2f_cc_40_c6
170141183460469231731687303715884105727
170_141183_460469_231731_687303_715884_105727

_42         // 식별자이지 정수 리터럴으로 취급되지 않는다
42_         // invalid: 언더 바는 연속된 숫자를 분리시켜야 한다
4__2        // invalid: 한 번에 하나의 언더 바만 사용할 수 있다
0_xBadFace  // invalid: 언더 바는 연속된 숫자를 분리시켜야 한다 (여기는 Optional Prefix를 분리시키고 있다)

 

부동 소수점 리터럴 (Floating-point Literals)

 

 부동 소수점 리터럴은 부동 소수점 상수의 10진수 또는 16진수 표현이다.

 

 10진수 부동 소수점 리터럴은 정수 부분(십진수), 소수점, 분수 부분(십진수) 및 지수 부분(e 또는 E 뒤에 선택적 기호 및 십진수 숫자가 옴)으로 구성된다. 정수 부분이나 소수 부분 중 하나가 생략될 수 있다. 소수점이나 지수 부분 중 하나가 생략될 수 있다. 지수 값 exp는 가수(정수 및 분수 부분)를 10exp만큼 확장한다.

 

 16진수 부동 소수점 리터럴은 0x 또는 0X 접두사, 정수 부분(16진수), 기수 부분(16진수), 지수 부분(p 또는 P 다음에 선택적 기호 및 10진수 숫자가 뒤따름)으로 구성된다. 정수 부분이나 소수 부분 중 하나가 생략될 수 있다. 기수점도 생략될 수 있지만 지수 부분은 필수이다. (이 구문은 IEEE 754-2008 §5.12.3에 제공된 구문과 일치한다.) 지수 값 exp는 가수(정수 및 분수 부분)를 2exp만큼 확장한다.

 

 가독성을 위해 기본 접두사 뒤나 연속 숫자 사이에 언더 바가 나타날 수 있다. 이러한 언더 바는 리터럴 값을 변경하지 않는다.

float_lit         = decimal_float_lit | hex_float_lit .

decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
                    decimal_digits decimal_exponent |
                    "." decimal_digits [ decimal_exponent ] .
decimal_exponent  = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .

hex_float_lit     = "0" ( "x" | "X" ) hex_mantissa hex_exponent .
hex_mantissa      = [ "_" ] hex_digits "." [ hex_digits ] |
                    [ "_" ] hex_digits |
                    "." hex_digits .
hex_exponent      = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0.
72.40
072.40       // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5.         // == 15.0
0.15e+0_2    // == 15.0

0x1p-2       // == 0.25
0x2.p10      // == 2048.0
0x1.Fp+0     // == 1.9375
0X.8p-0      // == 0.5
0X_1FFFP-16  // == 0.1249847412109375
0x15e-2      // == 0x15e - 2 (정수 빼기)

0x.p1        // invalid: 가수를 입력하지 않음
1p-2         // invalid: p 지수에는 16진수 가수가 필요
0x1.5e-2     // invalid: 16진수 가수에는 p 지수가 필요
1_.5         // invalid: 언더 바는 연속된 숫자를 분리해야 함
1._5         // invalid: 언더 바는 연속된 숫자를 분리해야 함
1.5_e1       // invalid: 언더 바는 연속된 숫자를 분리해야 함
1.5e_1       // invalid: 언더 바는 연속된 숫자를 분리해야 함
1.5e1_       // invalid: 언더 바는 연속된 숫자를 분리해야 함

 

허수 리터럴 (Imaginary Literals)

 

 허수 리터럴은 복소수 상수의 허수 부분을 나타낸다. 이는 정수 또는 부동 소수점 리터럴과 그 뒤에 소문자 i로 구성된다. 허수 리터럴의 값은 각 정수 또는 부동 소수점 리터럴의 값에 허수 단위 i를 곱한 값이다.

imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .

 이전 버전과의 호환성을 위해, 전체가 10진수로 구성된 허수 리터럴의 정수 부분은 0으로 시작하더라도 10진수 정수로 간주된다.

0i
0123i         // == 123i 이전 버전과의 호환성으로 인한 일치
0o123i        // == 0o123 * 1i == 83i
0xabci        // == 0xabc * 1i == 2748i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
0x1p-2i       // == 0x1p-2 * 1i == 0.25i

 

룬 리터럴 (Rune Literals)

 

 룬 리터럴은 유니코드 코드 포인트를 식별하는 정수 값은 룬 상수를 나타낸다. 룬 리터럴은 'x' 또는 '\n'과 같이 작은따옴표로 묶인 하나 이상의 문자로 표현된다. 작은따옴표 안에는 개행 문자와 이스케이프 처리되지 않은 작은 따옴표를 제외한 모든 문자가 나타날 수 있다. 작은따옴표 문자는 문자 자체의 유니코드 값을 나타내는 반면, 백슬래시로 시작하는 다중 문자 시퀀스는 값을 다양한 형식으로 인코딩한다.

 

 가장 간단한 형식으로는 작은따옴표 안의 단일 문자를 나타낸다. Go 소스 텍스트는 UTF-8로 인코딩된 유니코드 문자이므로 여러 UTF-8로 인코딩된 바이트는 단일 정수 값을 나타낼 수 있다. 예를 들어, 리터럴 'a'는 리터럴 a, 유니코드 U+0061, 값 0x61을 나타내는 단일 바이트를 보유하고, 'ä'는 리터럴 a-분음법, U+00E4, 값 0xe4를 나타내는 2바이트(0xc3 0xa4)를 보유한다.

 

 여러 백슬래시 이스케이프를 사용하면 임의의 값을 ASCII 텍스트로 인코딩할 수 있다. 정수 값을 숫자 상수로 표시하는 방법에는 네 가지가 있다. \x 뒤에는 정확하게 두 개의 16진수 문자가 온다. \u 뒤에는 정확히 4개의 16진수 문자가 온다. \U 뒤에는 정확히 8개의 16진수가 오고 일반 백슬래시 \ 뒤에는 정확히 3개의 8진수가 온다. 각 경우에 리터럴의 값은 해당 기수의 숫자로 표시되는 값이다.

 

 이러한 표현은 모두 정수로 생성되지만 유효한 범위는 다르다. 8진수 이스케이프는 0에서 255 사이의 값을 나타내야 한다. 16진수 이스케이프는 구성을 통해 이 조건을 충족한다. 이스케이프 \u 및 \U는 유니코드 코드 포인트를 나타내므로 그 안에 있는 일부 값, 특히 0x10FFFF 위의 값과 서로게이트 절반이 잘못된 값이다.

 

 백슬래시 뒤의 특정 단일 문자 이스케이프는 특수 값을 나타낸다.

\a   U+0007 alert or bell
\b   U+0008 backspace
\f   U+000C form feed
\n   U+000A line feed or newline
\r   U+000D carriage return
\t   U+0009 horizontal tab
\v   U+000B vertical tab
\\   U+005C backslash
\'   U+0027 single quote  (valid escape only within rune literals)
\"   U+0022 double quote  (valid escape only within string literals)

 

 룬 리터럴에서 백슬래시 뒤에 오는 인식할 수 없는 문자는 사용할 수 없다.

rune_lit         = "'" ( unicode_value | byte_value ) "'" .
unicode_value    = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value       = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value   = `\` "x" hex_digit hex_digit .
little_u_value   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value      = `\` "U" hex_digit hex_digit hex_digit hex_digit
                           hex_digit hex_digit hex_digit hex_digit .
escaped_char     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'\''         // rune literal containing single quote character
'aa'         // illegal: too many characters
'\k'         // illegal: k is not recognized after a backslash
'\xa'        // illegal: too few hexadecimal digits
'\0'         // illegal: too few octal digits
'\400'       // illegal: octal value over 255
'\uDFFF'     // illegal: surrogate half
'\U00110000' // illegal: invalid Unicode code point

 

문자열 리터럴 (String Literals)

 

 문자열 리터럴은 일련의 문자를 연결하여 얻은 문자열 상수를 나타낸다. 원시 문자열 리터럴과 해석된 문자열 리터럴의 두 가지 형식이 있다.

 

 원시 문자열 리터럴은 `foo`와 같이 역따옴표 사이의 문자 시퀀스이다. 따옴표 안에는 역따옴표를 제외한 모든 문자가 나타날 수 있다. 원시 문자열 리터럴의 값은 따옴표 사이에 해석되지 않은(암시적으로 UTF-8로 인코딩된) 문자로 구성된 문자열이다. 특히 백슬래시는 특별한 의미가 없으며 문자열에 개행 문자가 포함될 수 있다. 원시 문자열 리터럴 내의 캐리지 리턴 문자('\r')는 원시 문자열 값에서 삭제된다.

 

 해석된 문자열 리터럴은 "bar"와 같이 큰따옴표 사이의 문자 시퀀스이다. 따옴표 안에는 개행 문자와 이스케이프 처리되지 않은 큰따옴표를 제외한 모든 문자가 나타날 수 있다. 따옴표 사이의 텍스트는 리터럴의 값을 형성하며, 백슬래시 이스케이프는 룬 문자 그대로 해석되며(\'는 불법이고 \"는 합법적인 경우 제외) 동일한 제한 사항이 적용된다. 세 자리 8진수(\nnn) 두 자리 16진수(\xnn) 이스케이프는 결과 문자열의 개별 바이트를 나타낸다. 다른 모든 이스케이프는 개별 문자의 (아마도 멀티바이트) UTF-8 인코딩을 나타낸다. 따라서 문자열 리터럴 내부 \377 및 \xFF는 단일 값을 나타낸다. 값 0xFF=255는 바이트인 반면, ÿ, \u00FF, \U000000FF 및 \xc3\xbf는 문자 U+00FF의 UTF-8 인코딩의 두 바이트 0xc3 0xbf를 나타낸다.

 

string_lit             = raw_string_lit | interpreted_string_lit .
raw_string_lit         = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc`                // same as "abc"
`\n
\n`                  // same as "\\n\n\\n"
"\n"
"\""                 // same as `"`
"Hello, world!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800"             // illegal: surrogate half
"\U00110000"         // illegal: invalid Unicode code point

 

 아래 예제는 모두 동일한 문자열을 나타낸다.

"日本語"                                 // UTF-8 input text
`日本語`                                 // UTF-8 input text as a raw literal
"\u65e5\u672c\u8a9e"                    // the explicit Unicode code points
"\U000065e5\U0000672c\U00008a9e"        // the explicit Unicode code points
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"  // the explicit UTF-8 bytes

 

 소스 코드가 악센트와 문자를 포함하는 결합 형식과 같이 문자를 두 개의 코드 포인트로 나타내는 경우, 그 결과가 룬 리터럴에 배치되면 오류가 발생하고 문자열 리터럴에 배치되면 정상적으로 보이게 된다. 

 


맺음말

 

 Golang의 문법에 대해서 글을 작성하기 시작했는데, 이제 대략 15퍼센트 정도 작성했다. 아직 너무 많은 문법이 남아있어서 여기서 이만 줄이고 다음 글부터는 상수과 변수부터 시작해서 다시 글을 작성할 계획이다.

반응형

'Golang' 카테고리의 다른 글

Golang 입문기 - 문법 4  (2) 2024.02.11
Golang 입문기 - 문법 3  (2) 2023.11.20
Golang 입문기 - 문법 2  (2) 2023.10.27
Golang 입문기 - 0  (0) 2023.10.20