Wadler의 A Prettier Printer는 클래식한 기능성 진주입니다. 그러나 하스켈의 게으름 때문인지 내 게으름 때문인지 나는 그의 아이디어를 다른 언어로(또는 심지어 논문을 읽은 지 5분 만에 하스켈로) 다시 구현하는 데 어려움을 겪었습니다. 다행히 린디히는 이 문제를 인식하고 엄청 예뻐를 통해 대중에게 불을 지폈다. 하지만 그것조차도 나에게는 충분히 멍청하지 않았다.
하지만 두 논문의 아이디어를 고민하는 데 좀 더 시간을 보낸 후에 마침내 아이디어를 얻은 것 같습니다.
제목에서 알 수 있듯이 프리티 프린팅은 추상적인 "문서 언어"로 작성된 프로그램을 컴파일(및 실행)하는 과정으로 생각하겠습니다. 대부분의 프로그래밍 언어와 마찬가지로 이 문서 언어는
Doc—함께 구성할 수 있는 표현이 특징입니다. 이것은 인간이 추론하기 쉽게 만듭니다. Doc의 표현식을 일종의 어셈블리 언어(ASM)의 명령어로 컴파일합니다. ASM의 명령어는 문자열로 변환하기가 훨씬 쉽습니다.
다음은 프로세스의 개략도입니다.
구체적인 예로, 중첩 목록을 예쁘게 인쇄한다고 가정해 보겠습니다.
['onions', ['carrots', 'celery'], 'turnips']
이를 위한 Doc 프로그램은 다음과 같습니다.
group( '[' + nest( 4, br() + "'onions'" + ',' + br(' ') + group( '[' + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'") + br() + ']' ) + ',' + br(' ') + "'turnips'" ) + br() + ']' )
그룹, 둥지 등을 곧 만나보겠습니다. 지금은 문서 언어에 대한 일반적인 느낌을 얻는 것으로 충분합니다.
이 프로그램은 특정 "아키텍처 매개변수" 세트를 사용하여 ASM 명령어로 컴파일됩니다.
TEXT '[' LINE 4 TEXT "'onions'" TEXT ',' LINE 4 TEXT '[' TEXT '' TEXT "'carrots'" TEXT ',' TEXT ' ' TEXT "'celery'" TEXT '' TEXT ']' TEXT ',' LINE 4 TEXT "'turnips'" LINE 0 TEXT ']'
이는 문자열로 해석됩니다.
[ 'onions', ['carrots', 'celery'], 'turnips' ]
위에 언급된 중요한 기능 중 하나는 컴파일러에 목표 최대 선 너비인 구성 가능한 "아키텍처 매개변수"가 있다는 것입니다. 이 매개변수의 값에 따라 컴파일러는 동일한 Doc 프로그램에 대해 다른 ASM 명령을 내보냅니다. 위의 예에서는 대상 너비 30을 사용했습니다. 대신 20을 사용한 경우 생성된 어셈블리 지침은 결과 문자열과 마찬가지로 달라집니다.
[ 'onions', [ 'carrots', 'celery' ], 'turnips' ]
60을 사용하면 다음과 같습니다.
['onions', ['carrots', 'celery'], 'turnips']
어셈블리 언어는 간단하므로 먼저 다루겠습니다. 우리는 ASM 명령을 다음 두 가지 작업만 수행할 수 있는 매우 간단한 인쇄 장치를 제어하는 것으로 생각할 것입니다.
따라서 ASM은 두 가지 명령으로만 구성됩니다.
ASM 프로그램은 명령어를 하나씩 실행하여 문자열로 해석됩니다. 예를 들어 프로그램 실행을 추적해 보겠습니다.
['onions', ['carrots', 'celery'], 'turnips']
> 실행 중인 명령을 나타내고 아래에 현재 출력을 표시합니다. ^ 문자는 "프린터 헤드"의 현재 위치를 나타냅니다. 또한 공백을 나타내기 위해 _ 문자를 사용합니다. 그렇지 않으면 추적하기 어렵기 때문입니다.
첫 번째 TEXT 명령어는 'hello' 문자열을 내보내게 합니다.
group( '[' + nest( 4, br() + "'onions'" + ',' + br(' ') + group( '[' + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'") + br() + ']' ) + ',' + br(' ') + "'turnips'" ) + br() + ']' )
LINE 2는 다음 줄로 이동하고 머리 부분을 2칸 들여쓰기합니다.
TEXT '[' LINE 4 TEXT "'onions'" TEXT ',' LINE 4 TEXT '[' TEXT '' TEXT "'carrots'" TEXT ',' TEXT ' ' TEXT "'celery'" TEXT '' TEXT ']' TEXT ',' LINE 4 TEXT "'turnips'" LINE 0 TEXT ']'
그런 다음 TEXT '들여쓰기'로 인해 '들여쓰기'가 추가됩니다.
[ 'onions', ['carrots', 'celery'], 'turnips' ]
다음에 'world'가 오고 TEXT 'world'로 인해:
[ 'onions', [ 'carrots', 'celery' ], 'turnips' ]
LINE 0은 프린터를 다음 줄로 이동합니다(전혀 들여쓰기하지 않음).
['onions', ['carrots', 'celery'], 'turnips']
그리고 마지막으로 TEXT 'goodbye'는 'goodbye'를 방출합니다.
TEXT 'hello' LINE 2 TEXT 'indented' TEXT ' world' LINE 0 TEXT 'goodbye'
ASM 명령어를 "합계 유형"으로 표현하겠습니다.
즉,
> TEXT 'hello' LINE 2 TEXT 'indented' TEXT ' world' LINE 0 TEXT 'goodbye' == OUTPUT == hello ^
AsmInst 목록을 그들이 나타내는 문자열로 해석하는 것은 단지 지침을 반복하고 "올바른 일을 하는 것"의 문제입니다.
TEXT 'hello' > LINE 2 TEXT 'indented' TEXT ' world' LINE 0 TEXT 'goodbye' == OUTPUT == hello __ ^
TEXT 명령의 경우 해석기는 결과에 텍스트를 추가합니다. LINE 명령의 경우 인터프리터는 줄 바꿈('n')과 들여쓰기 공백을 추가합니다.
위 예제의 ASM 명령어를 Python으로 번역하여 해석을 테스트할 수 있습니다.
TEXT 'hello' LINE 2 > TEXT 'indented' TEXT ' world' LINE 0 TEXT 'goodbye' == OUTPUT == hello __indented ^
우리는 해석하기 쉽기 때문에 ASM을 좋아합니다. 하지만 사용하는 것은 고통스럽습니다. 이는 보다 인간 친화적인 Doc 언어에 동기를 부여합니다. ASM 프로그램은 명령의 순서인 반면, Doc 프로그램은 표현의 구성입니다. 이러한 표현은 다음 문법으로 요약됩니다.
TEXT 'hello' LINE 2 TEXT 'indented' > TEXT ' world' LINE 0 TEXT 'goodbye' == OUTPUT == hello __indented world ^
예:
TEXT 'hello' LINE 2 TEXT 'indented' TEXT ' world' > LINE 0 TEXT 'goodbye' == OUTPUT == hello __indented world ^
은 다음과 같은 Doc 표현식입니다.
TEXT 'hello' LINE 2 TEXT 'indented' TEXT ' world' LINE 0 > TEXT 'goodbye' == OUTPUT == hello __indented world goodbye ^
이것들은 무엇을 상징하나요?
예를 들면 다음과 같습니다.
AsmInst = str | int
다음 문자열을 나타냅니다.
def interpret(insts: list[AsmInst]) -> str: """Interpret the ASM instructions as a string.""" result = "" for inst in insts: match inst: case text if isinstance(text, str): result += inst case indent if isinstance(indent, int): result += f"\n{' ' * indent}" return result
두 번째로 더 복잡한 표현:
['onions', ['carrots', 'celery'], 'turnips']
다음 중 하나를 나타낼 수 있습니다.
group( '[' + nest( 4, br() + "'onions'" + ',' + br(' ') + group( '[' + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'") + br() + ']' ) + ',' + br(' ') + "'turnips'" ) + br() + ']' )
또는:
TEXT '[' LINE 4 TEXT "'onions'" TEXT ',' LINE 4 TEXT '[' TEXT '' TEXT "'carrots'" TEXT ',' TEXT ' ' TEXT "'celery'" TEXT '' TEXT ']' TEXT ',' LINE 4 TEXT "'turnips'" LINE 0 TEXT ']'
컴파일러에 제공된 목표 최대 선 너비 "아키텍처 매개변수"의 값에 따라 달라집니다. 따라서 br 표현식은 줄바꿈이나 일반 텍스트로 처리될 수 있으며, 이 경우 해당 텍스트 값이 사용됩니다(또는 텍스트 인수가 제공되지 않은 경우 '').
strs와 Python 클래스를 사용하여 Doc 표현식을 표현하겠습니다. 특히:
[ 'onions', ['carrots', 'celery'], 'turnips' ]
DocExpr DocExpr은 어떻습니까? 추가 Concat 클래스를 사용하여 이를 표현하겠습니다.
[ 'onions', [ 'carrots', 'celery' ], 'turnips' ]
우리는 표현식 결합을 위한 using을 지원하기를 원하므로 각 변형 클래스에 __add__ 및 __radd__를 구현해야 합니다. 를 사용하여 두 개의 Doc 표현식을 추가하면 둘의 Concat이 구성됩니다. 이 작업은 수동으로 수행하는 것이 매우 쉽습니다. 예:
['onions', ['carrots', 'celery'], 'turnips']
그러나 데코레이터를 정의하여 작업을 수행함으로써 타이핑 시간을 절약할 수 있습니다.
TEXT 'hello' LINE 2 TEXT 'indented' TEXT ' world' LINE 0 TEXT 'goodbye'
이제 변형 클래스는 다음과 같습니다.
> TEXT 'hello' LINE 2 TEXT 'indented' TEXT ' world' LINE 0 TEXT 'goodbye' == OUTPUT == hello ^
이제 우리의 임무는 목표 최대 선 너비를 고려하여 Doc 언어의 표현식을 동등한 ASM 명령어로 변환하는 컴파일러를 작성하는 것입니다.
TEXT 'hello' > LINE 2 TEXT 'indented' TEXT ' world' LINE 0 TEXT 'goodbye' == OUTPUT == hello __ ^
그러나 먼저 Doc 표현식을 중간 표현(IR) 언어의 표현식으로 "하위"한 다음 IR 표현식을 ASM으로 컴파일하는 것이 더 간단한 것으로 나타났습니다. 이 추가 "패스"를 도입하면 각 단계가 더 명확해집니다.
그래서 예쁜 인쇄 과정을 설명하는 회로도는 다소 지나치게 단순화되었습니다. 전체 사진은 다음과 같습니다.
IR 표현은 여러 면에서 Doc 표현과 유사합니다.
TEXT 'hello' LINE 2 > TEXT 'indented' TEXT ' world' LINE 0 TEXT 'goodbye' == OUTPUT == hello __indented ^
주요 차이점은 더 이상 표현식이 없다는 것입니다. 이러한 표현식은 하강 프로세스에서 IR 표현식 목록으로 변환됩니다. 실제로 이것이 모두 하강 패스의 역할입니다.
TEXT 'hello' LINE 2 TEXT 'indented' > TEXT ' world' LINE 0 TEXT 'goodbye' == OUTPUT == hello __indented world ^
먼저 IrExprs를 정의해야 합니다.
다음은 익숙해 보일 것입니다:
TEXT 'hello' LINE 2 TEXT 'indented' TEXT ' world' > LINE 0 TEXT 'goodbye' == OUTPUT == hello __indented world ^
낮은 작업은 Nil() 인스턴스를 빈 목록([])으로 바꾸고, Concat(car, cdr) 인스턴스를 car 및 cdr 표현식을 낮춘 결과를 추가하여 바꾸는 것입니다. Nest 및 Group의 하위 표현식에도 동일한 처리가 적용됩니다. 이는 재귀적인 "평탄화" 작업에 지나지 않습니다.
TEXT 'hello' LINE 2 TEXT 'indented' TEXT ' world' LINE 0 > TEXT 'goodbye' == OUTPUT == hello __indented world goodbye ^
위의 예제 Doc 표현식 중 하나를 사용하여 더 낮은 테스트:
AsmInst = str | int
정확히 우리가 기대하는 바입니다.
이제 마지막 단계: 컴파일입니다. 이 기능은 목표 최대 선 너비를 고려하여 IR 표현을 ASM 명령어로 변환합니다.
def interpret(insts: list[AsmInst]) -> str: """Interpret the ASM instructions as a string.""" result = "" for inst in insts: match inst: case text if isinstance(text, str): result += inst case indent if isinstance(indent, int): result += f"\n{' ' * indent}" return result
알고리즘의 대략적인 아이디어는 다음과 같습니다.
['onions', ['carrots', 'celery'], 'turnips']
과정에서 마법이 일어납니다.
group( '[' + nest( 4, br() + "'onions'" + ',' + br(' ') + group( '[' + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'") + br() + ']' ) + ',' + br(' ') + "'turnips'" ) + br() + ']' )
요약:
fits_Flat은 어떻게 작동하나요? 단순히 그룹의 지침을 따라 진행하며 brs를 텍스트로 처리하고 다음 중 하나의 경우에 중지합니다.
TEXT '[' LINE 4 TEXT "'onions'" TEXT ',' LINE 4 TEXT '[' TEXT '' TEXT "'carrots'" TEXT ',' TEXT ' ' TEXT "'celery'" TEXT '' TEXT ']' TEXT ',' LINE 4 TEXT "'turnips'" LINE 0 TEXT ']'
드디어 조각을 함께 클릭할 수 있습니다.
[ 'onions', ['carrots', 'celery'], 'turnips' ]
Pretty-Printer 인터페이스의 유일한 남은 부분은 문서 표현식 생성자입니다.
[ 'onions', [ 'carrots', 'celery' ], 'turnips' ]
소개에 나온 예시를 시도해 보겠습니다.
['onions', ['carrots', 'celery'], 'turnips']
여기에서 전체 소스를 확인하세요.
? 공사중 ?
? 공사중 ?
위 내용은 예쁜 프린팅은 편집이다의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!