구문 (프로그래밍 언어)
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
구문(프로그래밍 언어)은 컴퓨터 프로그래밍 언어의 규칙을 정의하며, 코드가 올바르게 작성되었는지 확인하는 데 사용된다. 구문은 일반적으로 어휘, 구문, 컨텍스트의 세 단계로 나뉜다. 어휘 수준은 문자를 토큰으로, 구문 수준은 토큰을 구문 트리로, 컨텍스트 수준은 변수 이름과 유형을 확인한다. 구문 오류는 코드의 형식이 잘못되었을 때 발생하며, 괄호 불일치, 잘못된 문자 사용, 예약어 사용, 세미콜론 누락 등이 일반적인 예시이다. 구문은 정규 표현식과 EBNF를 사용하여 정의되며, 유효한 프로그램의 형식을 설명하지만, 프로그램의 의미는 의미론에서 처리된다.
컴퓨터 언어 구문은 일반적으로 어휘적 수준, 구문적 수준, 문맥적 수준의 세 단계로 구분된다.[3] 이러한 구분은 각 단계를 독립적으로 처리하여 모듈성을 높인다.
구문 오류는 프로그래밍 언어의 규칙을 따르지 않아 발생하는 오류이다. 예를 들어, `(add 1 1)`은 문법적으로 올바른 Lisp 프로그램이지만, `(_ 1 1)`은 어휘 오류, `(add 1 1`은 닫는 괄호 ')'가 없어 파싱 오류가 발생한다.[4][5][6]
2. 구문의 단계
| 단계 | 촘스키 위계 | 문법 종류 | 문법 표현 | 설명 |
| ---------------- | ------------- | ------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 어휘적 수준 | 정규 언어 | Type-3 문법 | 정규 표현식 | 문자들이 모여 토큰을 형성하는 방식을 다룬다. |
| 구문적 수준 | 문맥 자유 언어 | Type-2 문법 | BNF의 생산 규칙 | 토큰들이 모여 구를 형성하는 방식을 다룬다. |
| 문맥적 수준 | 문맥 의존 문법 | (일반적으로 수동 처리) | 이름 확인 규칙, 타입 검사 | 객체나 변수 이름이 무엇을 가리키는지, 타입이 유효한지 등을 확인한다. 즉, 프로그램의 의미를 분석하며, 이 단계에서 이름 확인과 타입 검사 등이 이루어진다. |
각 단계별 분석은 lex-yacc와 같은 자동화 도구를 이용하거나, Haskell, Python, C 등 다양한 언어를 사용하여 수동으로 구현할 수 있다.
2. 1. 어휘적 수준 (Lexical Level)
컴퓨터 언어 구문은 일반적으로 세 가지 수준으로 구분되는데, 그중 어휘적 수준은 문자들이 어떻게 토큰을 형성하는지를 결정한다.[3] 이 수준은 촘스키 위계에서 정규 언어에 해당하며, 어휘 문법에 지정된다. 어휘 문법은 보통 정규 표현식으로 주어지는 Type-3 문법이다.[3]
렉싱은 렉서가 일련의 문자를 일련의 토큰으로 변환하는 과정이다.[3]
lex-yacc는 정규 표현식으로 작성된 어휘 명세와 BNF로 작성된 구문 문법에서 렉서와 파서를 자동으로 생성하는 대표적인 도구이다.
2. 2. 구문적 수준 (Syntactic Level)
컴퓨터 언어 구문은 일반적으로 다음 세 가지 수준으로 구분된다.[3]
이러한 방식으로 구분하면 각 수준을 별도로, 종종 독립적으로 설명하고 처리할 수 있어 모듈성이 생긴다.
파싱 단계는 다음 두 부분으로 나눌 수 있다.
AST 및 컨텍스트 분석 단계는 구문에 의미와 해석을 추가하므로 의미 분석의 한 형태로 간주될 수 있다.
이 수준은 촘스키 위계의 수준에 해당한다.
lex-yacc와 같은 도구를 사용하여 정규 표현식과 BNF로 작성된 구문 문법에서 렉서와 파서를 자동으로 생성할 수 있다.
2. 3. 문맥적 수준 (Contextual Level)
문맥적 수준은 객체나 변수 이름이 무엇을 참조하는지, 타입이 유효한지 등을 결정한다.[3] 즉, 프로그램의 의미를 분석하는 단계이다. 이 단계에서는 이름 확인과 타입 검사 등이 이루어진다.
이 모듈성은 때때로 가능하지만, 많은 실제 언어에서는 이전 단계가 이후 단계에 의존한다. 예를 들어, C의 렉서 해킹은 토큰화가 컨텍스트에 따라 달라지기 때문이다.[3] 이러한 경우에도 구문 분석은 종종 이 이상적인 모델에 근접하는 것으로 간주된다.
원칙적으로 컨텍스트 구조는 문맥 의존 문법으로 설명할 수 있으며, 속성 문법과 같은 수단을 통해 자동 분석할 수 있지만, 일반적으로 이 단계는 이름 확인 규칙과 타입 검사를 통해 수동으로 수행되며, 각 범위에 대한 이름과 유형을 저장하는 심볼 테이블을 통해 구현된다.[3]
3. 구문 오류 (Syntax Error)
형식 오류나 선언되지 않은 변수 오류도 컴파일 시간에 발견되면 구문 오류로 간주되기도 하지만, 일반적으로는 의미론적 오류로 분류한다.
`'a' + 1` (파이썬)과 같이 문자열과 정수를 더할 수 없는 형식 오류는, 컴파일러가 구문 분석 중 감지할 수도 있지만, 컴파일러가 더 일반적인 규칙을 사용하고 나중에 형식 검사를 하는 경우도 있다. `a + b`와 같이 동적으로 형식이 지정되는 언어에서는 런타임에만 형식 오류를 발견할 수 있다. 파이썬에서 변수는 형식이 없고 값만 가지기 때문이다. 컴파일러가 감지한 형식 오류를 구문 오류로 볼 것인지에 대해서는 의견이 다를 수 있지만, 실행 시간에만 감지되는 형식 오류는 일반적으로 의미론적 오류로 간주된다.
3. 1. 흔한 구문 오류의 예
4. 구문 정의 (Syntax Definition)
텍스트 기반 프로그래밍 언어의 구문은 일반적으로 정규 표현식(어휘 분석을 위한 어휘 구조)과 바커스-나우르 표기법(문맥 자유 문법을 위한 메타언어)을 조합하여 정의된다.[12] 이들은 구문 범주(비단말 기호)와 ''단말'' 기호를 귀납적으로 지정한다.[1] 구문 범주는 ''생성 규칙''에 의해 정의되며, 이는 특정 구문 범주에 속하는 값을 지정한다.[1] 단말 기호는 ''define'', ''if'', ''let'', ''void''와 같은 키워드처럼 구문상 유효한 프로그램을 구성하는 구체적인 문자 또는 문자열이다.
구문은 문맥 자유 구문과 문맥 의존 구문으로 나눌 수 있다.[12] 문맥 자유 구문은 프로그래밍 언어의 메타언어에 의해 지시되는 규칙으로, 구문의 해당 부분을 둘러싸거나 참조하는 문맥에 의해 제한되지 않는다. 반면 문맥 의존 구문은 문맥에 따라 제한된다.
언어는 동등한 정규 표현식(어휘 수준) 또는 동일한 언어를 생성하는 다른 구문 규칙과 같이 여러 개의 동등한 문법을 가질 수 있다. 더 넓은 범주의 문법(예: LR 문법)을 사용하면 더 제한된 범주(예: LL 문법)에 비해 더 짧거나 간단한 문법을 사용할 수 있지만, LL 문법은 규칙이 더 많은 더 긴 문법이 필요할 수 있다. 다른 동등한 구문 문법은 다른 구문 분석 트리를 생성하지만 기본 언어(유효한 문서 집합)는 동일하다.
리스프 구문은 정규 표현식과 확장 배커스-나우르 표기법을 사용하여 정의할 수 있으며, 표현식, 원자, 숫자, 심볼, 리스트에 대한 리스프 구문은 다음과 같다.
expression = atom | list
atom = number | symbol
number = [+-]?['0'-'9']+
symbol = ['A'-'Z''a'-'z'].*
list = '(', expression*, ')'
이 문법은 다음을 지정한다.
- ''표현식''은 ''원자'' 또는 ''리스트''이다.
- ''원자''는 ''숫자'' 또는 ''심볼''이다.
- ''숫자''는 선택적으로 더하기 또는 빼기 기호로 시작하는 하나 이상의 십진수 시퀀스이다.
- ''심볼''은 문자 다음에 0개 이상의 문자가 오는 문자열이다 (공백 제외).
- ''리스트''는 괄호의 짝으로, 그 안에 0개 이상의 ''표현식''이 있다.
이 문법에서 잘 구성된 토큰 시퀀스의 예는 다음과 같다: '
12345
', '()
', '(A B C232 (1))
'4. 1. 정규 표현식 (Regular Expression)
정규 표현식은 어휘적 수준에서 토큰을 정의하는 데 사용된다.[12] 예를 들어, 숫자는 `[0-9]+`와 같이 표현될 수 있는데, 이는 0부터 9까지의 숫자가 하나 이상 연속되는 패턴을 의미한다.[12]리스프 구문에서 숫자는 다음과 같이 정의된다.[1]
```text
number = [+-]?['0'-'9']+
```
이는 선택적으로 더하기(+) 또는 빼기(-) 기호로 시작하고, 그 뒤에 하나 이상의 0부터 9까지의 숫자가 오는 패턴을 의미한다.[1]
4. 2. 확장 배커스-나우르 표기법 (EBNF)
확장 배커스-나우르 표기법(EBNF)은 문맥 자유 문법을 표현하는 데 사용되며, 구문적 수준에서 프로그램의 구조를 정의한다. 다음은 정규 표현식과 확장 배커스-나우르 표기법(EBNF)을 사용하여 정의된 Lisp의 S-표현식 데이터 구문 예시이다.expression = atom | list
atom = number | symbol
number = [+-]?['0'-'9']+
symbol = ['A'-'Z']['A'-'Z''0'-'9'].*
list = '(', expression*, ')'
이 문법은 다음을 나타낸다.
- ''표현식(expression)''은 ''원자(atom)'' 또는 ''리스트(list)''이다.
- ''원자''는 ''숫자(number)'' 또는 ''기호(symbol)''이다.
- ''숫자''는 선택적으로 더하기(+) 또는 빼기(-) 기호로 시작하는 하나 이상의 십진수(0-9)의 연속이다.
- ''심볼''은 문자(A-Z) 다음에 0개 이상의 문자(A-Z, 0-9)가 오는 문자열이다. (공백 제외)
- ''리스트''는 괄호 '(' 와 ')' 쌍으로, 그 안에 0개 이상의 ''표현식''이 포함된다.
이 문법에서 올바르게 구성된 토큰의 예시는 다음과 같다: '
12345
', '()
', '(A B C232 (1))
'[12]5. 구문과 의미론 (Syntax vs. Semantics)
프로그래밍 언어의 구문은 프로그램의 형식을 설명하지만, 프로그램의 의미나 실행 결과에 대한 정보는 제공하지 않는다. 기호 조합에 부여되는 의미는 의미론(형식 또는 참조 구현에 하드 코딩됨)에 의해 처리된다. 의미론이 의미를 부여하기 위해서는 먼저 유효한 구문이 확립되어야 한다.[12]
예를 들어, `(add 1 1)`은 구문적으로 유효한 Lisp 프로그램이다. 이는 1과 1을 더하는 연산을 나타낸다. 그러나 다음과 같은 코드는 유효하지 않다.
- `(_ 1 1)` : 어휘 오류. '_'는 유효하지 않은 문자이다.
- `(add 1 1` : 파싱 오류. 닫는 괄호 ')'가 누락되었다.
형식 오류와 선언되지 않은 변수 오류는 컴파일 시간에 감지될 때 구문 오류로 간주되기도 하지만, 일반적으로 의미론적 오류로 분류된다.[4][5][6]
파이썬 코드 `'a' + 1`는 문자열 리터럴에 정수 리터럴을 더하려 하기 때문에 형식 오류가 발생한다. 이러한 오류는 컴파일러에 따라 구문 분석 중 또는 컨텍스트 분석(형식 검사) 중에 감지될 수 있다. 동적으로 형식이 지정되는 언어에서는 런타임에만 형식 오류가 감지될 수 있다.
구문적으로 올바른 프로그램이더라도 의미론적으로는 오류가 있을 수 있다. 많은 구문적으로 올바른 프로그램이 언어 규칙에 따라 부적절하며, 번역 또는 실행 시 오류가 발생할 수 있다. 경우에 따라 정의되지 않은 동작을 유발할 수도 있다.
자연어를 예로 들면, 문법적으로 올바른 문장이 의미가 없거나 거짓일 수 있다.
- "무색 녹색 아이디어가 격렬하게 잠든다."는 문법적으로는 올바르지만 의미가 없다.
- "존은 결혼한 독신자이다."는 문법적으로는 올바르지만 거짓이다.
다음 C 언어 코드는 구문적으로는 올바르지만, `p`가 널 포인터이므로 `p->real`과 `p->im` 연산은 의미가 없어 정의되지 않은 동작을 수행한다.
```c
complex *p = NULL;
complex abs_p = sqrt (p->real * p->real + p->im * p->im);
```
```c
int x;
printf("%d", x);
```
위의 C 언어 코드는 구문적으로 유효하지만 초기화되지 않은 변수를 사용하므로 의미적으로 정의되지 않았다.
5. 1. 의미론적 오류의 예 (프로그래밍)
C, C++ 등의 언어에서는 초기화되지 않은 변수를 사용하면 의미론적 오류가 발생할 수 있다. 초기화되지 않은 변수는 어떤 값을 가질지 예측하기 어렵기 때문에 프로그램이 예상치 못한 동작을 일으킬 수 있다.[4][5][6]대부분의 프로그래밍 언어에서 0으로 나누는 것은 의미론적 오류에 해당하며, 일반적으로 프로그램 실행 중단을 유발한다.[4][5][6]
정적 자료형 언어에서는 자료형 불일치가 의미론적 오류를 일으킨다. 예를 들어 정수형 변수에 문자열 값을 할당하려 하면 컴파일 과정에서 오류가 발생한다.[4][5][6]
6. 복잡한 문법 (Complex Grammars)
대부분의 프로그래밍 언어 구문은 문맥 자유 문법(Type-2)으로 표현할 수 있다.[7] 그러나 변수 선언 및 유효 범위(scope) 등으로 인해 전체 구문은 문맥 의존적(Type-1)이다. 예외적으로 일부 언어는 Type-0 (튜링 완전) 구문 문법을 갖는다.
Perl이나 Lisp과 같은 일부 언어는 파싱 단계에서 코드가 실행되는 것을 허용한다. 또한 프로그래머가 파서의 동작을 변경할 수 있는 기능도 제공한다. 이러한 특징 때문에 파싱과 실행의 구분이 모호해지며, 구문 분석이 결정 불가능 문제가 되기도 한다. 즉, 파싱 단계가 끝나지 않을 수도 있다. 예를 들어, Perl은 `BEGIN` 문을 통해 파싱 중 코드를 실행할 수 있고, Perl 함수 프로토타입은 코드의 구문 해석 및 유효성을 변경할 수 있다.[8][9] 이는 "Perl만이 Perl을 파싱할 수 있다" 또는 "Perl조차도 Perl을 파싱할 수 없다"라는 말로 표현된다. Lisp 매크로 지시어도 `defmacro` 구문을 통해 파싱 중 실행되므로, Lisp 컴파일러는 전체 Lisp 런타임 시스템을 포함해야 한다. 반면 C 매크로는 문자열 대체일 뿐이어서 코드 실행이 필요하지 않다.[10][11]
7. 참고: 촘스키 위계 (Chomsky Hierarchy)
촘스키 위계는 형식 언어와 문법을 분류하는 체계이다.[3] 이 위계는 컴퓨터 언어 구문의 분석 단계를 이해하는 데 도움을 준다.
단계 | 설명 |
---|---|
단어 (Words) | 정규 언어(Regular Language)에 해당하며, 어휘 문법(Lexical Grammar)에 의해 정의된다. Type-3 문법이며, 보통 정규 표현식(Regular Expression)으로 표현된다. |
구문 (Syntax) | 문맥 자유 언어(Context-Free Language, CFL)에 해당하며, 일반적으로 결정적 문맥 자유 언어(Deterministic Context-Free Language, DCFL)이다. 구조 문법(Phrase Structure Grammar)에 의해 정의되며, Type-2 문법이고, 보통 Backus–Naur 형식(BNF)의 생산 규칙(Production Rule)으로 표현된다. |
문맥 (Context) | 문맥 의존 문법(Context-Sensitive Grammar)으로 설명될 수 있으며, 속성 문법(Attribute Grammar) 등을 통해 자동으로 분석될 수 있다. 하지만, 일반적으로 이름 확인(Name Resolution) 규칙과 타입 검사(Type Checking)를 통해 수동으로 처리되며, 심볼 테이블(Symbol Table)을 통해 구현된다. |
참조
[1]
서적
Essentials of Programming Languages
The MIT Press
1992
[2]
서적
Designing Maintainable Software
Springer Science & Business Media
[3]
간행물
A Systematic Literature Review of Lexical Analyzer Implementation Techniques in Compiler Design
https://papers.ssrn.[...]
2020-12-31
[4]
서적
Compilers: Principles, Techniques, and Tools
https://archive.org/[...]
Addison Wesley
2007
[5]
서적
Compiler Construction: Principles and Practice
Brooks/Cole
1997
[6]
웹사이트
Semantic Errors in Java
http://www.dummies.c[...]
[7]
서적
Introduction to the Theory of Computation
https://archive.org/[...]
PWS Publishing
[8]
웹사이트
LtU comment clarifying that the undecidable problem is membership in the class of Perl programs
http://lambda-the-ul[...]
[9]
웹사이트
chromatic's example of Perl code that gives a syntax error depending on the value of random variable
http://www.modernper[...]
[10]
웹인용
An Introduction to Common Lisp Macros
http://www.apl.jhu.e[...]
Apl.jhu.edu
1996-02-08
[11]
웹사이트
The Common Lisp Cookbook - Macros and Backquote
http://cl-cookbook.s[...]
Cl-cookbook.sourceforge.net
2007-01-16
[12]
서적
Formal Syntax and Semantics of Programming Languages
Addison-Wesley Publishing Company
[13]
웹사이트
Issue of syntax or semantics?
https://stackoverflo[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com