>  기사  >  백엔드 개발  >  예쁜 프린팅은 편집이다

예쁜 프린팅은 편집이다

DDD
DDD원래의
2024-11-01 04:21:02511검색

Wadler의 A Prettier Printer는 클래식한 기능성 진주입니다. 그러나 하스켈의 게으름 때문인지 내 게으름 때문인지 나는 그의 아이디어를 다른 언어로(또는 심지어 논문을 읽은 지 5분 만에 하스켈로) 다시 구현하는 데 어려움을 겪었습니다. 다행히 린디히는 이 문제를 인식하고 엄청 예뻐를 통해 대중에게 불을 지폈다. 하지만 그것조차도 나에게는 충분히 멍청하지 않았다.

하지만 두 논문의 아이디어를 고민하는 데 좀 더 시간을 보낸 후에 마침내 아이디어를 얻은 것 같습니다.

개요

제목에서 알 수 있듯이 프리티 프린팅은 추상적인 "문서 언어"로 작성된 프로그램을 컴파일(및 실행)하는 과정으로 생각하겠습니다. 대부분의 프로그래밍 언어와 마찬가지로 이 문서 언어는
Doc—함께 구성할 수 있는 표현이 특징입니다. 이것은 인간이 추론하기 쉽게 만듭니다. Doc의 표현식을 일종의 어셈블리 언어(ASM)의 명령어로 컴파일합니다. ASM의 명령어는 문자열로 변환하기가 훨씬 쉽습니다.

다음은 프로세스의 개략도입니다.

Pretty-Printing is Compilation

구체적인 예로, 중첩 목록을 예쁘게 인쇄한다고 가정해 보겠습니다.

['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 명령을 다음 두 가지 작업만 수행할 수 있는 매우 간단한 인쇄 장치를 제어하는 ​​것으로 생각할 것입니다.

  1. 텍스트 문자열을 내보냅니다.
  2. 다음 줄로 넘어가서 일정량 들여쓰기

따라서 ASM은 두 가지 명령으로만 구성됩니다.

  1. TEXT - 텍스트 문자열을 내보냅니다.
  2. LINE - 프린터를 다음 줄로 이동한 후 들여쓰기 공백으로 들여쓰기합니다.

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 명령은 Python 문자열로 표시됩니다.
  • LINE 명령어는 정수로 표시됩니다.

즉,

> 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
       ^

이것들은 무엇을 상징하나요?

  • Python str 리터럴은 자신을 나타냅니다.
  • br()는 가능한 줄바꿈입니다.
  • Nest(indent, doc)는 들여쓰기 공백으로 시각적으로 오프셋되는 "중첩" 하위 표현식을 생성합니다.
  • group(doc)은 모든 br()이 줄 바꿈으로 처리되거나 처리되지 않는 하위 표현식을 구분합니다.
  • Doc 표현식을 결합합니다.
  • nil은 "빈" 표현으로 작동합니다.

예를 들면 다음과 같습니다.

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으로 컴파일하는 것이 더 간단한 것으로 나타났습니다. 이 추가 "패스"를 도입하면 각 단계가 더 명확해집니다.

중간 표현

그래서 예쁜 인쇄 과정을 설명하는 회로도는 다소 지나치게 단순화되었습니다. 전체 사진은 다음과 같습니다.

Pretty-Printing is Compilation

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

알고리즘의 대략적인 아이디어는 다음과 같습니다.

  • 컴파일러는 일부 "상태" 정보를 유지합니다.
    • 현재(가로) 줄 위치
    • 현재 들여쓰기 크기
    • br을 줄 바꿈으로 처리해야 하는지 아니면 "플랫"으로 렌더링해야 하는지 여부
  • 표현식을 반복하여 일부 ASM 명령을 내보내고 줄 위치를 적절하게 업데이트합니다.
['onions', ['carrots', 'celery'], 'turnips']

과정에서 마법이 일어납니다.

group(
    '['
    + nest(
        4,
        br()
        + "'onions'"
        + ','
        + br(' ')
        + group(
            '['
            + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'")
            + br()
            + ']'
        )
        + ','
        + br(' ')
        + "'turnips'"
    )
    + br()
    + ']'
)

요약:

  • 텍스트 표현식의 경우 TEXT 명령어를 내보내고 텍스트 길이만큼 위치(pos)를 전진시킵니다.
  • br 표현식은 flat 값에 따라 처리됩니다.
    • 플랫이 True인 경우 텍스트로 처리합니다.
    • 그렇지 않으면 현재 들여쓰기 수준으로 INDENT 명령을 내보내고 위치를 이 값으로 재설정합니다.
  • 중첩 표현식의 경우 모든 하위 표현식을 처리하지만 현재 들여쓰기 수준은 중첩 표현식의 들여쓰기 값만큼 증가합니다.
  • 마지막으로 그룹 표현의 경우 먼저 나머지 공간을 초과하지 않고 전체 그룹을 평면적으로 렌더링할 수 있는지 확인합니다. 이는 그룹화된 모든 하위 표현식에 대한 flat 값을 결정하고, brs가 줄 바꿈(또는 텍스트)으로 렌더링되는지 여부를 결정합니다.

fits_Flat은 어떻게 작동하나요? 단순히 그룹의 지침을 따라 진행하며 brs를 텍스트로 처리하고 다음 중 하나의 경우에 중지합니다.

  • 공간이 부족합니다(너비 < 0). 이 경우 그룹화된 하위 표현식을 단순하게 렌더링할 수 없습니다.
  • 모든 하위 표현식을 처리했으며, 이 경우 그룹은 플랫으로 렌더링될 수 있습니다.
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']

여기에서 전체 소스를 확인하세요.

Doc에서 "프로그래밍"하는 방법

? 공사중 ?

  • 일반적인 패턴.
  • BR, Nest, Group이 상호작용하는 방식

종소리와 휘파람

? 공사중 ?

  • 강제접기에 접기 매개변수 그룹을 추가합니다.

위 내용은 예쁜 프린팅은 편집이다의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.