저는 항상 컴파일러와 파서에 관심이 많았습니다. 컴파일러의 개념과 전반적인 프레임워크도 알고 있지만 세부적인 내용은 잘 모릅니다. 우리가 작성하는 프로그램 소스 코드는 실제로 일련의 문자입니다. 컴파일러나 인터프리터는 이 문자 시퀀스를 직접 이해하고 실행할 수 있습니다. 이 기사에서는 Python을 사용하여 소규모 목록 조작 언어(파이썬의 목록과 유사)를 해석하는 간단한 파서를 구현합니다. 사실, 컴파일러와 인터프리터는 기본 이론만 이해하면 구현이 비교적 간단합니다(물론 제품 수준의 컴파일러나 인터프리터는 여전히 매우 복잡합니다).
이 목록 언어에서 지원되는 작업:
veca = [1, 2, 3] # 列表声明 vecb = [4, 5, 6] print 'veca:', veca # 打印字符串、列表,print expr+ print 'veca * 2:', veca * 2 # 列表与整数乘法 print 'veca + 2:', veca + 2 # 列表与整数加法 print 'veca + vecb:', veca + vecb # 列表加法 print 'veca + [11, 12]:', veca + [11, 12] print 'veca * vecb:', veca * vecb # 列表乘法 print 'veca:', veca print 'vecb:', vecb
해당 출력:
veca: [1, 2, 3] veca * 2: [2, 4, 6] veca + 2: [1, 2, 3, 2] veca + vecb: [1, 2, 3, 2, 4, 5, 6] veca + [11, 12]: [1, 2, 3, 2, 11, 12] veca * vecb: [4, 5, 6, 8, 10, 12, 12, 15, 18, 8, 10, 12] veca: [1, 2, 3, 2] vecb: [4, 5, 6]
컴파일러와 인터프리터가 입력 문자 스트림을 처리할 때 기본적으로 인간이 문장을 이해하는 방식과 일치합니다. 예:
I love you.
영어를 처음 배우는 경우 먼저 각 단어의 의미를 알아야 하며, 그런 다음 각 단어의 품사를 주어-술어-목적어 구조에 맞게 분석하여 의미를 이해할 수 있어야 합니다. 이 문장의. 이 문장은 어휘분열에 따라 어휘단위스트림이 얻어지는 것이며, 실제로 이것이 문자스트림에서 어휘단위스트림으로의 변환을 완성하는 것이다. 품사를 분석하여 주어-동사-목적어 구조를 결정하는 것은 영어 문법에 따라 이 구조를 파악하는 것이며, 이것이 문법 분석이며, 입력된 어휘 단위 흐름을 기반으로 문법 파싱 트리를 식별한다. 마지막으로 단어의 의미와 문법적 구조를 결합하여 최종적으로 문장의 의미를 도출하는 것이 의미론적 분석이다. 컴파일러와 인터프리터의 처리 과정은 비슷하지만 약간 더 복잡합니다. 여기서는 인터프리터에만 중점을 둡니다.
우리는 아주 간단한 작은 언어를 구현하고 있을 뿐이므로 구문 트리 생성과 그에 따른 복잡한 의미 분석이 포함되지 않습니다. 다음으로 어휘 분석과 구문 분석에 대해 살펴보겠습니다.
어휘 분석과 구문 분석은 각각 어휘 파서와 구문 파서에 의해 완료됩니다. 이 두 파서는 구조와 기능이 비슷합니다. 둘 다 입력 시퀀스를 입력으로 사용하여 특정 구조를 식별합니다. 어휘 파서는 소스 코드 문자 스트림에서 토큰(어휘 단위)을 하나씩 구문 분석하는 반면, 구문 파서는 하위 구조와 어휘 단위를 식별한 후 일부 처리를 수행합니다. 두 파서는 모두 LL(1) 재귀 하강 파서를 통해 구현될 수 있습니다. 이 유형의 파서로 완료되는 단계는 다음과 같습니다. 절의 유형을 예측하고, 하위 구조와 일치하도록 구문 분석 함수를 호출하고, 어휘 단위를 일치시킨 다음, 다음과 같이 코드를 삽입합니다. 필요합니다. 사용자 정의 작업을 수행합니다.
다음은 LL(1)에 대한 간략한 소개입니다. LL(1)은 구문 분석을 위해 구문 분석 트리를 사용합니다. 예: x = x 2;
이 트리에서 x, =, 2와 같은 리프 노드를 터미널 노드라고 하고, 나머지 노드를 비터미널 노드라고 합니다. LL(1)을 구문 분석할 때 특정 트리 데이터 구조를 만들 필요가 없습니다. 각 비종단 노드에 대해 구문 분석 함수를 작성하고 해당 노드를 만날 때 이를 호출하면 구문 분석을 통과할 수 있습니다. 구문 분석 트리 정보를 얻기 위해 호출 시퀀스(트리 탐색과 동일)에서 함수를 사용합니다. LL(1)이 파싱되면 루트 노드부터 리프 노드까지 순서대로 실행되므로 이는 "내림차순" 프로세스이고 파싱 함수는 자신을 호출할 수 있으므로 "재귀적"이므로 LL( 1) 재귀 하강 파서라고도 합니다.
LL(1)의 두 L은 모두 왼쪽에서 오른쪽을 의미합니다. 첫 번째 L은 파서가 입력 내용을 왼쪽에서 오른쪽으로 구문 분석함을 의미하고 두 번째 L은 내림차순 프로세스 중에 입력도 분석함을 의미합니다. 콘텐츠는 왼쪽에서 오른쪽으로 하위 노드를 순회하며, 1은 1개의 예측 단위를 기반으로 예측을 한다는 의미입니다.
목록 미니 언어의 구현을 살펴보겠습니다. 첫 번째는 언어의 문법입니다. 문법은 언어를 설명하는 데 사용되며 파서의 설계 사양으로 간주됩니다.
statlist: stat+ stat: ID '=' expr | 'print' expr (, expr)* expr: multipart ('+' multipart)* | STR multipart: primary ('*' primary)* primary: INT | ID | '[' expr (',', expr)* ']' INT: (1..9)(0..9)* ID: (a..z | A..Z)* STR: (\".*\") | (\'.*\')
这是用DSL描述的文法,其中大部分概念和正则表达式类似。"a|b"表示a或者b,所有以单引号括起的字符串是关键字,比如:print,=等。大写的单词是词法单元。可以看到这个小语言的文法还是很简单的。有很多解析器生成器可以自动根据文法生成对应的解析器,比如:ANTRL,flex,yacc等,这里采用手写解析器主要是为了理解解析器的原理。下面看一下这个小语言的解释器是如何实现的。
首先是词法解析器,完成字符流到token流的转换。采用LL(1)实现,所以使用1个向前看字符预测匹配的token。对于像INT、ID这样由多个字符组成的词法规则,解析器有一个与之对应的方法。由于语法解析器并不关心空白字符,所以词法解析器在遇到空白字符时直接忽略掉。每个token都有两个属性类型和值,比如整型、标识符类型等,对于整型类型它的值就是该整数。语法解析器需要根据token的类型进行预测,所以词法解析必须返回类型信息。语法解析器以iterator的方式获取token,所以词法解析器实现了next_token方法,以元组方式(type, value)返回下一个token,在没有token的情况时返回EOF。
''''' A simple lexer of a small vector language. statlist: stat+ stat: ID '=' expr | 'print' expr (, expr)* expr: multipart ('+' multipart)* | STR multipart: primary ('*' primary)* primary: INT | ID | '[' expr (',', expr)* ']' INT: (1..9)(0..9)* ID: (a..z | A..Z)* STR: (\".*\") | (\'.*\') Created on 2012-9-26 @author: bjzllou ''' EOF = -1 # token type COMMA = 'COMMA' EQUAL = 'EQUAL' LBRACK = 'LBRACK' RBRACK = 'RBRACK' TIMES = 'TIMES' ADD = 'ADD' PRINT = 'print' ID = 'ID' INT = 'INT' STR = 'STR' class Veclexer: ''''' LL(1) lexer. It uses only one lookahead char to determine which is next token. For each non-terminal token, it has a rule to handle it. LL(1) is a quit weak parser, it isn't appropriate for the grammar which is left-recursive or ambiguity. For example, the rule 'T: T r' is left recursive. However, it's rather simple and has high performance, and fits simple grammar. ''' def __init__(self, input): self.input = input # current index of the input stream. self.idx = 1 # lookahead char. self.cur_c = input[0] def next_token(self): while self.cur_c != EOF: c = self.cur_c if c.isspace(): self.consume() elif c == '[': self.consume() return (LBRACK, c) elif c == ']': self.consume() return (RBRACK, c) elif c == ',': self.consume() return (COMMA, c) elif c == '=': self.consume() return (EQUAL, c) elif c == '*': self.consume() return (TIMES, c) elif c == '+': self.consume() return (ADD, c) elif c == '\'' or c == '"': return self._string() elif c.isdigit(): return self._int() elif c.isalpha(): t = self._print() return t if t else self._id() else: raise Exception('not support token') return (EOF, 'EOF') def has_next(self): return self.cur_c != EOF def _id(self): n = self.cur_c self.consume() while self.cur_c.isalpha(): n += self.cur_c self.consume() return (ID, n) def _int(self): n = self.cur_c self.consume() while self.cur_c.isdigit(): n += self.cur_c self.consume() return (INT, int(n)) def _print(self): n = self.input[self.idx - 1 : self.idx + 4] if n == 'print': self.idx += 4 self.cur_c = self.input[self.idx] return (PRINT, n) return None def _string(self): quotes_type = self.cur_c self.consume() s = '' while self.cur_c != '\n' and self.cur_c != quotes_type: s += self.cur_c self.consume() if self.cur_c != quotes_type: raise Exception('string quotes is not matched. excepted %s' % quotes_type) self.consume() return (STR, s) def consume(self): if self.idx >= len(self.input): self.cur_c = EOF return self.cur_c = self.input[self.idx] self.idx += 1 if __name__ == '__main__': exp = ''''' veca = [1, 2, 3] print 'veca:', veca print 'veca * 2:', veca * 2 print 'veca + 2:', veca + 2 ''' lex = Veclexer(exp) t = lex.next_token() while t[0] != EOF: print t t = lex.next_token()
运行这个程序,可以得到源代码:
veca = [1, 2, 3] print 'veca:', veca print 'veca * 2:', veca * 2 print 'veca + 2:', veca + 2
对应的token序列:
('ID', 'veca') ('EQUAL', '=') ('LBRACK', '[') ('INT', 1) ('COMMA', ',') ('INT', 2) ('COMMA', ',') ('INT', 3) ('RBRACK', ']') ('print', 'print') ('STR', 'veca:') ('COMMA', ',') ('ID', 'veca') ('print', 'print') ('STR', 'veca * 2:') ('COMMA', ',') ('ID', 'veca') ('TIMES', '*') ('INT', 2) ('print', 'print') ('STR', 'veca + 2:') ('COMMA', ',') ('ID', 'veca') ('ADD', '+') ('INT', 2)
接下来看一下语法解析器的实现。语法解析器的的输入是token流,根据一个向前看词法单元预测匹配的规则。对于每个遇到的非终结符调用对应的解析函数,而终结符(token)则match掉,如果不匹配则表示有语法错误。由于都是使用的LL(1),所以和词法解析器类似, 这里不再赘述。
''''' A simple parser of a small vector language. statlist: stat+ stat: ID '=' expr | 'print' expr (, expr)* expr: multipart ('+' multipart)* | STR multipart: primary ('*' primary)* primary: INT | ID | '[' expr (',', expr)* ']' INT: (1..9)(0..9)* ID: (a..z | A..Z)* STR: (\".*\") | (\'.*\') example: veca = [1, 2, 3] vecb = veca + 4 # vecb: [1, 2, 3, 4] vecc = veca * 3 # vecc: Created on 2012-9-26 @author: bjzllou ''' import veclexer class Vecparser: ''''' LL(1) parser. ''' def __init__(self, lexer): self.lexer = lexer # lookahead token. Based on the lookahead token to choose the parse option. self.cur_token = lexer.next_token() # similar to symbol table, here it's only used to store variables' value self.symtab = {} def statlist(self): while self.lexer.has_next(): self.stat() def stat(self): token_type, token_val = self.cur_token # Asignment if token_type == veclexer.ID: self.consume() # For the terminal token, it only needs to match and consume. # If it's not matched, it means that is a syntax error. self.match(veclexer.EQUAL) # Store the value to symbol table. self.symtab[token_val] = self.expr() # print statement elif token_type == veclexer.PRINT: self.consume() v = str(self.expr()) while self.cur_token[0] == veclexer.COMMA: self.match(veclexer.COMMA) v += ' ' + str(self.expr()) print v else: raise Exception('not support token %s', token_type) def expr(self): token_type, token_val = self.cur_token if token_type == veclexer.STR: self.consume() return token_val else: v = self.multipart() while self.cur_token[0] == veclexer.ADD: self.consume() v1 = self.multipart() if type(v1) == int: v.append(v1) elif type(v1) == list: v = v + v1 return v def multipart(self): v = self.primary() while self.cur_token[0] == veclexer.TIMES: self.consume() v1 = self.primary() if type(v1) == int: v = [x*v1 for x in v] elif type(v1) == list: v = [x*y for x in v for y in v1] return v def primary(self): token_type = self.cur_token[0] token_val = self.cur_token[1] # int if token_type == veclexer.INT: self.consume() return token_val # variables reference elif token_type == veclexer.ID: self.consume() if token_val in self.symtab: return self.symtab[token_val] else: raise Exception('undefined variable %s' % token_val) # parse list elif token_type == veclexer.LBRACK: self.match(veclexer.LBRACK) v = [self.expr()] while self.cur_token[0] == veclexer.COMMA: self.match(veclexer.COMMA) v.append(self.expr()) self.match(veclexer.RBRACK) return v def consume(self): self.cur_token = self.lexer.next_token() def match(self, token_type): if self.cur_token[0] == token_type: self.consume() return True raise Exception('expecting %s; found %s' % (token_type, self.cur_token[0])) if __name__ == '__main__': prog = ''''' veca = [1, 2, 3] vecb = [4, 5, 6] print 'veca:', veca print 'veca * 2:', veca * 2 print 'veca + 2:', veca + 2 print 'veca + vecb:', veca + vecb print 'veca + [11, 12]:', veca + [11, 12] print 'veca * vecb:', veca * vecb print 'veca:', veca print 'vecb:', vecb ''' lex = veclexer.Veclexer(prog) parser = Vecparser(lex) parser.statlist()
运行代码便会得到之前介绍中的输出内容。这个解释器极其简陋,只实现了基本的表达式操作,所以不需要构建语法树。如果要为列表语言添加控制结构,就必须实现语法树,在语法树的基础上去解释执行。

다음 단계를 통해 Numpy를 사용하여 다차원 배열을 만들 수 있습니다. 1) Numpy.array () 함수를 사용하여 NP.Array ([[1,2,3], [4,5,6]]과 같은 배열을 생성하여 2D 배열을 만듭니다. 2) np.zeros (), np.ones (), np.random.random () 및 기타 함수를 사용하여 특정 값으로 채워진 배열을 만듭니다. 3) 서브 어레이의 길이가 일관되고 오류를 피하기 위해 배열의 모양과 크기 특성을 이해하십시오. 4) NP.Reshape () 함수를 사용하여 배열의 모양을 변경하십시오. 5) 코드가 명확하고 효율적인지 확인하기 위해 메모리 사용에주의를 기울이십시오.

BroadcastingInnumpyIsamethodtoperformoperationsonArraysoffferentShapesByAutomicallyAligningThem.itsimplifiesCode, enourseadability, andboostsperformance.here'showitworks : 1) smalraysarepaddedwithonestomatchdimenseare

forpythondatastorage, chooselistsforflexibilitywithmixeddatatypes, array.arrayformemory-effic homogeneousnumericaldata, andnumpyarraysforadvancednumericalcomputing.listsareversatilebutlessefficipforlargenumericaldatasets.arrayoffersamiddlegro

pythonlistsarebetterthanarraysformanagingDiversEdatatypes.1) 1) listscanholdementsofdifferentTypes, 2) thearedynamic, weantEasyAdditionSandremovals, 3) wefferintufiveOperationsLikEslicing, but 4) butiendess-effectorlowerggatesets.

toaccesselementsInapyThonArray : my_array [2] AccessHetHirdElement, returning3.pythonuseszero 기반 인덱싱 .1) 사용 positiveAndnegativeIndexing : my_list [0] forthefirstelement, my_list [-1] forstelast.2) audeeliciforarange : my_list

기사는 구문 모호성으로 인해 파이썬에서 튜플 이해의 불가능성에 대해 논의합니다. 튜플을 효율적으로 생성하기 위해 튜플 ()을 사용하는 것과 같은 대안이 제안됩니다. (159 자)

이 기사는 파이썬의 모듈과 패키지, 차이점 및 사용법을 설명합니다. 모듈은 단일 파일이고 패키지는 __init__.py 파일이있는 디렉토리이며 관련 모듈을 계층 적으로 구성합니다.

기사는 Python의 Docstrings, 사용법 및 혜택에 대해 설명합니다. 주요 이슈 : 코드 문서 및 접근성에 대한 문서의 중요성.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

MinGW - Windows용 미니멀리스트 GNU
이 프로젝트는 osdn.net/projects/mingw로 마이그레이션되는 중입니다. 계속해서 그곳에서 우리를 팔로우할 수 있습니다. MinGW: GCC(GNU Compiler Collection)의 기본 Windows 포트로, 기본 Windows 애플리케이션을 구축하기 위한 무료 배포 가능 가져오기 라이브러리 및 헤더 파일로 C99 기능을 지원하는 MSVC 런타임에 대한 확장이 포함되어 있습니다. 모든 MinGW 소프트웨어는 64비트 Windows 플랫폼에서 실행될 수 있습니다.

SublimeText3 영어 버전
권장 사항: Win 버전, 코드 프롬프트 지원!

SublimeText3 Linux 새 버전
SublimeText3 Linux 최신 버전

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

Atom Editor Mac 버전 다운로드
가장 인기 있는 오픈 소스 편집기
