구문(프로그래밍 언어)

Syntax (programming languages)
구문 강조 표시와 들여쓰기 스타일은 종종 프로그래머들이 소스 코드의 요소들을 인식하는데 도움을 주기 위해 사용된다.파이톤 코드는 컬러 코딩된 강조표시를 사용한다.

컴퓨터 과학에서 컴퓨터 언어구문은 그 언어에서 올바른 구조화된 문장이나 표현으로 간주되는 기호들의 조합을 정의하는 규칙들의 집합이다.이는 문서가 소스 코드를 나타내는 프로그래밍 언어와 문서가 데이터를 나타내는 마크업 언어 모두에 적용된다.

언어의 구문은 그 언어의 표면 형태를 규정한다.[1]텍스트 기반 컴퓨터 언어는 문자 시퀀스에 기초하는 반면, 시각적 프로그래밍 언어는 공간 배치와 기호 사이의 연결(문자형 또는 그래픽일 수 있음)에 기초한다.구문적으로 유효하지 않은 문서는 구문 오류가 있다고 한다.언어의 구문을 설계할 때 설계자는 이러한 예에서 일반적인 규칙을 알아내기 전에 법적 문자열과 불법 문자열의 예를 모두 적는 것으로 시작할 수 있다.[2]

따라서 구문은 코드의 형태를 가리키며 의미론, 즉 의미와 대비된다.컴퓨터 언어를 처리함에 있어서 의미적 처리는 일반적으로 통사적 처리 후에 이루어지지만, 어떤 경우에는 완전한 통사적 분석을 위해서는 의미적 처리가 필요하며, 이것들은 함께 또는 동시에 이루어진다.컴파일러에서, 통사적 분석은 프런트엔드를 구성하는 반면, 의미적 분석은 백엔드(그리고 이 단계가 구별되는 경우 중간 끝)를 구성한다.

구문 수준

컴퓨터 언어 구문은 일반적으로 세 가지 수준으로 구분된다.

  • 단어 – 캐릭터가 토큰을 형성하는 방법을 결정하는 어휘적 수준
  • 구문 – 토큰이 구문을 형성하는 방법을 결정하는 문법 수준, 좁게 말하기;
  • 컨텍스트 – 개체 또는 변수 이름이 가리키는 유형 결정, 유효한 유형인지 여부 등

이러한 방식으로 구분하면 모듈화가 이루어지며, 각 수준을 개별적으로 그리고 자주 독립적으로 기술하고 처리할 수 있다.첫째로, 렉서(Lexer)는 문자의 선형 시퀀스를 토큰의 선형 시퀀스로 변환한다. 이를 "독소 분석" 또는 "lexing"이라고 한다.둘째로, 파서는 토큰의 선형 순서를 계층적 구문 트리로 변환한다; 이것은 좁게 말하면 "파싱"이라고 알려져 있다.셋째, 문맥 분석은 이름을 결정하고 유형을 확인한다.이러한 모듈화는 때때로 가능하지만, 많은 실제 언어에서 초기 단계는 이후의 단계에 의존한다. 예를 들어, C의 렉서 해킹은 토큰화가 상황에 따라 다르기 때문이다.이러한 경우에도 구문 분석은 종종 이 이상적인 모델에 근접한 것으로 보여진다.

파싱 단계 자체는 파스 트리, 즉 문법에 의해 결정되지만 일반적으로 실용화하기에는 너무 세부적인 '구문 트리'와 이를 사용 가능한 형태로 단순화하는 추상 구문 트리(AST)의 두 부분으로 나눌 수 있다.AST와 문맥 분석 단계는 구문에 의미와 해석을 추가하거나 또는 공식적으로 기술하거나 구현하기 어렵거나 어색할 수 있는 구문 규칙의 비공식적인 수동 구현으로서 의미 분석의 한 형태로 간주할 수 있다.

레벨은 일반적으로 촘스키 계급의 레벨에 해당한다.단어들은 정규 언어로 되어 있는데, 일반적으로 정규 표현으로 주어지는 3종 문법인 어휘 문법에 명시되어 있다.구절은 상황 없는 언어(CFL), 일반적으로 결정론적인 상황 없는 언어(DCFL)로, 형식 2 문법인 구절 구조 문법에 명시되어 있으며, 일반적으로 BNF(Backus–Naur) 형태제작 규칙으로 주어진다.구문 그래머는 종종 구문 분석하기 쉽도록 전체 문맥이 없는 문법보다 훨씬 더 제약이 있는 문법에서 지정된다. 반면에 LR 파서는 모든 DCFL을 선형 시간에 구문 분석할 수 있는 반면, 단순 LALR 파서와 심지어 더 간단한 LL 파서는 더 효율적이지만 생산 규칙이 제약된 문법만 구문 분석할 수 있다.원칙적으로 문맥 구조는 문맥에 민감한 문법에 의해 기술될 수 있으며, 속성 그래머와 같은 수단에 의해 자동 분석되지만, 일반적으로 이 단계는 이름 결정 규칙과 유형 확인을 통해 수동으로 수행되며, 각 범위에 대한 이름과 유형을 저장하는 기호 를 통해 구현된다.

정규 표현식으로 작성된 어휘사양서와 BNF에서 작성된 문법의 파서(파서)에서 자동으로 렉서(Lexer)를 생성하는 도구가 작성되었다. 이는 절차적 또는 기능적 프로그래밍을 필요로 하지 않고 선언적 프로그래밍을 사용할 수 있게 한다.대표적인 예가 렉시야크 쌍이다.이러한 트리는 자동으로 구체적인 구문 트리를 생성하며, 파서 작성자는 이를 추상 구문 트리로 변환하는 방법을 설명하는 코드를 수동으로 작성해야 한다.상황별 분석도 일반적으로 수동으로 실시한다.이러한 자동 도구의 존재에도 불구하고, 파싱은 여러 가지 이유로 수작업으로 구현되는 경우가 많은데, 아마도 문구 구조가 문맥이 없는 것이 아니거나, 대체 구현이 성능이나 오류 보고를 향상시키거나, 문법을 보다 쉽게 변경할 수 있도록 한다.파서들은 종종 하스켈과 같은 기능 언어 또는 파이톤이나 펄과 같은 스크립팅 언어 또는 C나 C+++로 쓰여진다.

오류의 예

예를 들면,(add 1 1)구문적으로 유효한 Lisp 프로그램('add' 기능이 있다고 가정하고, 그렇지 않으면 이름 확인이 실패함), 1과 1을 추가한다.그러나 다음 사항은 유효하지 않다.

(_ 1) 어휘 오류: '_'이(가) 올바르지 않음(1 1 구문 분석 오류 추가: '마감 누락')

렉서는 첫 번째 오류를 식별할 수 없다는 점에 유의하십시오. 렉서는 '_'로 시작하는 단어 규칙이 없으므로 토큰 LEFT_PAREN을 생산한 후 프로그램의 나머지 '()가 유효하지 않다는 것만 알고 있다.두 번째 오류는 구문 분석 단계에서 감지된다.파서는 (유일한 일치로) 토큰으로 인해 "list" 생산 규칙을 식별했고, 따라서 오류 메시지를 줄 수 있으며, 일반적으로 모호할 수 있다.

형식 오류와 발표되지 않은 변수 오류는 컴파일 시간(강력한 형식의 언어를 컴파일할 때 주로 해당함)에 감지될 때 구문 오류로 간주되기도 하지만, 대신 이러한 종류의 오류를 의미 오류로 분류하는 것이 일반적이다.[3][4][5]

예를 들어 Python 코드는

'a' + 1

문자열 리터럴을 정수 리터럴에 추가하기 때문에 형식 오류가 있다.이러한 유형의 오류는 컴파일 시간에 감지할 수 있다.컴파일러가 "LiteralOrIdentifier + LiteralIdentifier + LiteralIdentifier" 형식의 모든 표현을 허용하는 구문 분석 규칙을 사용할 가능성이 높지만 "IntegerLiteral + 정수Literal"은 허용하지 않는 별도의 규칙을 사용할 경우 구문 분석(구문 분석) 중에 검출할 수 있다.상황별 분석(유형 확인 발생 시) 중 감지된다.어떤 경우에는 컴파일러가 이 유효성 검사를 수행하지 않으며, 이러한 오류는 런타임에만 감지된다.

활자를 런타임에만 결정할 수 있는 동적으로 입력된 언어에서, 많은 유형 오류는 런타임에만 감지될 수 있다.예를 들어 Python 코드

a + b

구문 수준에서 구문론적으로 유효하지만, 변수에 Python에는 유형이 없고 값만 있기 때문에 a와 b 유형의 정확성은 런타임에만 결정될 수 있다.컴파일러가 검출한 형식 오류를 (정적인 의미 오류가 아닌) 구문 오류라고 해야 하는지에 대해서는 의견이 분분하지만, 프로그램 실행 시간에만 검출할 수 있는 형식 오류는 항상 구문 오류가 아닌 의미론적 오류로 간주된다.

구문 정의

삽입 토큰화가 있는 파이썬 코드의 파스 트리

텍스트 프로그래밍 언어의 구문은 보통 정규식( 어휘 구조의 경우)과 백커스-나우르 형식(문법 구조의 경우)의 조합을 사용하여 구문 범주(비단자)와 단자 기호를 유도적으로 지정한다.통사적 범주는 생산이라는 규칙에 의해 정의되는데, 이것은 특정 통사적 범주에 속하는 값을 명시한다.[1]터미널 기호는 구문론적으로 유효한 프로그램이 구성되는 구체적인 문자 또는 문자 문자열(예: 정의, if, let 또는 void같은 키워드)이다.

언어는 등가 정규식( 어휘적 수준에서)과 같은 다른 등가 문법이나 동일한 언어를 생성하는 다른 구문 규칙을 가질 수 있다.LR 그래머와 같은 광범위한 범주의 그래머를 사용하면 더 많은 규칙을 가진 더 긴 그래머를 필요로 하는 LL 문법과 같이 더 제한적인 범주에 비해 더 짧거나 단순한 그래머를 허용할 수 있다.기본 언어(유효한 문서의 집합)는 동일하지만 다르지만 동등한 구문 그래머는 다른 파스 트리를 산출한다.

예: Lisp S-expressions

아래는 정규 표현법과 확장 백커스-나우르 형식을 사용하여 정의한 간단한 문법이다.그것은 S-expression의 구문인 프로그래밍 언어 Lisp의 데이터 구문을 기술하며, 이 구문은 구문론 범주 표현식, 원자, 숫자, 기호리스트에 대한 생산을 정의한다.

표현= 원자의  리스트를 작성하다 원자의= 번호를 붙이다  심볼 번호를 붙이다= [+-]?['0'-'9']+ 심볼= ['A'-'Z']['A'-'Z'0'-'9'].* 리스트를 작성하다= '(', 표현*, ')' 

이 문법은 다음을 명시한다.

  • 표현원자 또는 목록이다.
  • 원자숫자 또는 기호 중 하나이다.
  • 숫자는 하나 이상의 소수 자릿수의 중단되지 않은 시퀀스로, 선택적으로 더하기 또는 빼기 기호가 선행된다.
  • 기호는 0자 이상의 문자 뒤에 오는 문자(공백 제외)이다.
  • 리스트는 일치하는 괄호 쌍으로, 안에 0개 이상의 식이 있다.

여기서 소수점, 대문자와 소문자, 괄호는 단자 기호다.

이 문법에서 잘 형성된 토큰 시퀀스의 예는 다음과 같다.'12345', '()', '(A B C232 (1))'

복합 문법

프로그래밍 언어를 지정하는 데 필요한 문법은 촘스키 서열에서 그것의 위치에 따라 분류될 수 있다.대부분의 프로그래밍 언어의 문법은 Type-2 문법을 사용하여 지정할 수 있다. 즉, 전체 구문은 (변수 선언과 중첩된 범위 때문에) 문맥에 민감하지만,[6] 따라서 Type-1은 문맥 없는 문법이다.단, 예외도 있으며, 일부 언어의 경우 문법 구문은 Type-0(튜링 완료)이다.

Perl 및 Lisp와 같은 일부 언어에서 해당 언어의 규격(또는 구현)은 구문 분석 단계에서 실행되는 구성을 허용한다.게다가, 이 언어들은 프로그래머가 파서의 행동을 바꿀 수 있도록 하는 구조를 가지고 있다.이 조합은 파싱과 실행의 구분을 효과적으로 흐리게 하며, 구문 분석을 이들 언어에서 이해할 수 없는 문제로 만들고, 파싱 단계가 끝나지 않을 수도 있다는 것을 의미한다.예를 들어, Perl에서는 다음 명령을 사용하여 구문 분석하는 동안 코드를 실행할 수 있다.BEGIN문장, 그리고 Perl 함수 프로토타입은 통사적 해석, 그리고 나머지 코드의 통사적 타당성을 변경할 수 있다.[7]구어적으로 이것을 "Perl만이 Perl을 파스할 수 있다"(파싱하는 동안 코드를 실행해야 하고 문법을 수정할 수 있기 때문에), 또는 더 강하게 "Perl도 Perl을 파스할 수 없다"(미독할 수 없기 때문에).마찬가지로, Lisp 매크로도 에 의해 도입되었다.defmacro구문도 구문 분석 중에 실행되는데, 이는 Lisp 컴파일러가 전체 Lisp 런타임 시스템이 존재해야 함을 의미한다.이와는 대조적으로 C 매크로는 문자열 교체에 불과하며 코드를 실행할 필요가 없다.[8][9]

구문 대 의미론

언어의 구문은 유효한 프로그램의 형태를 설명하지만, 프로그램의 의미나 그 프로그램을 실행한 결과에 대한 어떠한 정보도 제공하지 않는다.기호의 조합에 주어진 의미는 의미론(참조 구현에서 형식적이거나 하드 코딩됨)에 의해 처리된다.모든 구문론적으로 올바른 프로그램이 의미론적으로 옳은 것은 아니다.그럼에도 불구하고 많은 구문론적으로 올바른 프로그램들은 언어의 규칙에 따라 잘못된 형태를 띠고 있으며, (언어 규격과 구현의 건전성에 따라) 번역이나 실행에 오류가 발생할 수 있다.어떤 경우에는 그러한 프로그램이 정의되지 않은 행동을 보일 수 있다.어떤 프로그램이 언어 내에서 잘 정의되어 있을 때에도, 그것은 그것을 쓴 사람이 의도하지 않은 의미를 여전히 가지고 있을 수 있다.

자연어를 예로 들면 문법적으로 올바른 문장에 의미를 부여할 수 없거나 문장이 거짓일 수 있다.

  • "색 없는 녹색 사상은 격하게 잠을 잔다."는 문법적으로는 잘 형성되어 있지만 일반적으로 받아들여지는 의미는 없다.
  • "존은 결혼한 총각이다." 문법적으로는 잘 형성되어 있지만, 사실일 수 없는 의미를 표현하고 있다.

다음 C 언어 파편은 구문론적으로 정확하지만 의미론적으로 정의되지 않은 연산을 수행한다(이 때문에).pnull 포인터, 작업p->real그리고p->im의미가 없다:

 복합적인 *p = NULL;  복합적인 abs_p = sqrt (p->진짜 * p->진짜 + p->나는 * p->나는); 

더 간단한 예로,

 인트로 x;  활자화하다("%d", x); 

초기화되지 않은 변수를 사용하기 때문에 구문론적으로 유효하지만 의미론적으로 정의되지는 않는다.일부 프로그래밍 언어(예: Java 및 C#)의 컴파일러가 이러한 종류의 초기화되지 않은 변수 오류를 감지하더라도 구문 오류보다는 의미론적 오류로 간주해야 한다.[5][10]

참고 항목

다양한 프로그래밍 언어의 구문을 신속하게 비교하려면 "Hello, World!" 프로그램 예를 참조하십시오.

참조

  1. ^ a b Friedman, Daniel P.; Mitchell Wand; Christopher T. Haynes (1992). Essentials of Programming Languages (1st ed.). The MIT Press. ISBN 0-262-06145-7.
  2. ^ Smith, Dennis (1999). Designing Maintainable Software. Springer Science & Business Media.
  3. ^ Aho, Alfred V.; Monica S. Lam; Ravi Sethi; Jeffrey D. Ullman (2007). Compilers: Principles, Techniques, and Tools (2nd ed.). Addison Wesley. ISBN 0-321-48681-1.4.1.3: 구문 오류 처리, 페이지.194–195.
  4. ^ Louden, Kenneth C. (1997). Compiler Construction: Principles and Practice. Brooks/Cole. ISBN 981-243-694-4. 연습 1.3 페이지 27–28.
  5. ^ a b Java의 의미 오류
  6. ^ Michael Sipser (1997). Introduction to the Theory of Computation. PWS Publishing. ISBN 0-534-94728-X. 섹션 2.2: 푸시다운 오토마타, 페이지 101–114.
  7. ^ 다음의 논의는 예를 제시한다.
  8. ^ "An Introduction to Common Lisp Macros". Apl.jhu.edu. 1996-02-08. Archived from the original on 2013-08-06. Retrieved 2013-08-17.
  9. ^ "The Common Lisp Cookbook - Macros and Backquote". Cl-cookbook.sourceforge.net. 2007-01-16. Retrieved 2013-08-17.
  10. ^ 구문이나 의미론 문제?

외부 링크