
# 아키텍처

Phasis는 기회가 있을 때 바이트코드 VM을 사용하는 4단계 트리 워커입니다. 데이터 흐름은 다음과 같습니다:

```
JS text
  │
  ▼
┌──────────┐
│  Lexer   │  src/Lexer/
└────┬─────┘
     ▼ tokens
┌──────────┐
│  Parser  │  src/Parser/
└────┬─────┘
     ▼ AST nodes
┌──────────────┐
│ Interpreter  │  src/Runtime/
│  + Bytecode  │  src/Bytecode/
│      VM      │
└────┬─────────┘
     ▼ JsValue
PHP host
```

각 단계는 다음 단계가 사용하는 작은 표면을 가진 PHP 클래스입니다. 링크 단계는 없습니다. 파싱 결과로 AST가 생성되고 인터프리터가 이를 직접 순회합니다.

## 렉서

`src/Lexer/Lexer.php`는 소스 텍스트를 `Token` 인스턴스 스트림으로 변환합니다. 각 토큰은 읽기 전용 구조체이며 타입(`TokenType` 열거형, 약 80개 사례), 값, 줄, 열을 포함합니다.

렉서는 다음을 처리합니다:

- 숫자 리터럴 — 10진수, 16진수(`0x`), 8진수(`0o`), 2진수(`0b`), 지수 표기, 숫자 구분자.
- 문자열 리터럴 — 작은따옴표와 큰따옴표, 모든 이스케이프 시퀀스, 중첩 표현식이 포함된 템플릿 리터럴.
- 정규 표현식 리터럴 — `/pattern/flags` 토큰화 전체를 지원하며, `/`를 정규식으로 볼지 나눗셈으로 볼지 판단합니다.
- 식별자, 키워드, 구두점.
- 주석(한 줄 및 블록)은 트리비아로 보존됩니다.
- Unicode 소스 — UTF-8 바이트를 JS UTF-16 소스로 해석합니다.

출력은 토큰을 지연 생성하는 제너레이터입니다. 파서는 한 토큰을 미리 읽어 이를 소비합니다.

## 파서

`src/Parser/Parser.php`는 직접 작성한 Pratt 파서입니다. 식의 우선순위는 `src/Parser/Precedence.php`에 정의되어 있습니다. 문은 자체 재귀 하강 방식으로 처리됩니다.

`src/Ast/`에서 불변 AST 노드를 생성합니다. 각 노드에는 스택 추적과 오류 보고를 위한 소스 위치가 포함됩니다.

주요 구조:

- **표현식**: `Literal`, `Identifier`, `BinaryExpression`, `UnaryExpression`, `AssignmentExpression`, `CallExpression`, `MemberExpression`, `ArrowFunction`, `ObjectExpression`, `ArrayExpression`, `ConditionalExpression`, `TemplateLiteral`, `SpreadElement`, `NewExpression`, `ThisExpression`, `SequenceExpression`, `TaggedTemplate`, `ClassExpression`, `YieldExpression`, `AwaitExpression`.
- **문**: `BlockStatement`, `IfStatement`, `ForStatement`, `ForInStatement`, `ForOfStatement`, `WhileStatement`, `DoWhileStatement`, `SwitchStatement`, `ReturnStatement`, `ThrowStatement`, `TryStatement`, `BreakStatement`, `ContinueStatement`, `ExpressionStatement`.
- **선언**: `VariableDeclaration`, `FunctionDeclaration`, `ClassDeclaration`, `ImportDeclaration`.
- **패턴**(구조 분해): `ArrayPattern`, `ObjectPattern`, `RestElement`, `AssignmentPattern`.

자동 세미콜론 삽입은 사양에 따라 렉서가 아닌 파서에서 구현됩니다.

## 인터프리터

`src/Runtime/Interpreter.php`는 AST 워커입니다. 각 노드를 방문해 `JsValue`를 반환하거나 제어 흐름을 변경하는 문에 대해서는 `Completion`를 반환합니다.

주요 지원 클래스:

- **`Environment`** (`src/Runtime/Environment.php`) — 렉시컬 스코프 체인입니다. 함수 호출마다 렉시컬 부모를 가리키는 새 환경을 만듭니다. `var`는 함수 스코프로 호이스팅되고, `let` / `const`는 TDZ가 적용되는 블록 스코프입니다.
- **`CallStack`** (`src/Runtime/CallStack.php`) — 깊이 제한이 있는 명시적 스택입니다. 함수와 메서드 호출은 프레임을 푸시하고, 예외가 발생하면 프레임이 해제됩니다.
- **`Completion`** (`src/Runtime/Completion.php`) — `normal`, `return`, `throw`, `break`, `continue`의 판별 유니온과 선택적 값 및 레이블입니다.
- **`Reference`** (`src/Runtime/Reference.php`) — 할당과 `delete`에서 좌변을 평가하는 사양의 Reference 타입입니다.

## 값

JavaScript 값은 `src/Value/` 아래의 PHP 객체입니다. 각각 `JsValue` 인터페이스를 구현하고 자체 타입 변환 메서드(`toNumber()`, `toString()`, `toBoolean()`)를 가집니다.

| 클래스 | JS 타입 |
|---|---|
| `JsUndefined` | `undefined`(싱글턴) |
| `JsNull` | `null`(싱글턴) |
| `JsBoolean` | `true` / `false` |
| `JsNumber` | IEEE 754 배정밀도. NaN, ±Infinity, ±0 보존 |
| `JsString` | UTF-16 문자열 의미 체계 |
| `JsObject` | 속성 맵과 프로토타입 체인을 가진 기본 객체 |
| `JsArray` | 길이 추적 기능을 포함하도록 `JsObject` 확장 |
| `JsFunction` | 캡처한 `Environment`가 있는 클로저 |
| `JsSymbol` | Symbol 원시 값 |
| `JsBigInt` | bcmath를 통한 임의 정밀도 |
| `JsProxy` | 13개 트랩 모두 지원, 취소 가능 |
| `JsPromise` | 동기 실행기 + 마이크로태스크 큐 |
| `JsGenerator` | 이중 모드: 프레임 스냅샷(바이트코드로 컴파일된 본문. [바이트코드 VM § 프레임 스냅샷 제너레이터](https://phasis.inline0.dev/ko/docs/advanced/bytecode-vm) 참조)이며, `yield*`, yield가 포함된 try, 비동기 제너레이터에는 PHP Fiber 대체 방식을 사용합니다 |
| `JsMap`, `JsSet`, `JsWeakMap`, `JsWeakSet`, `JsWeakRef`, `JsFinalizationRegistry` | 컬렉션 |
| `JsArrayBuffer`, `JsSharedArrayBuffer`, `JsDataView`, `JsTypedArray` | 바이너리 데이터 |

숫자 처리는 역사적으로 버그가 가장 많이 발생한 영역입니다. `NaN !== NaN`는 참이어야 하고 `-0 === 0`도 참이어야 하며, ToNumber의 경계 사례는 비트 수준까지 정확히 처리해야 합니다. `Spec/TypeConversion` 클래스는 사양 알고리즘을 그대로 구현하며 철저한 테스트를 거쳤습니다.

## 내장 라이브러리

`src/BuiltIn/`는 모든 표준 라이브러리 객체(`Array`, `String`, `Object`, `Math`, `JSON`, `Date`, `RegExp`, `Map`, `Set`, `Promise`, `Proxy`, `Reflect`, `Symbol`, `BigInt`, `TypedArray`, `Temporal`, `Intl`, `console`)와 웹 플랫폼 및 Fetch 팩(`URL`, `Headers`, `Request`, `Response`, `Blob`, `FormData`, 전체 Streams, `EventTarget`, `AbortController`/`AbortSignal`, `DOMException`, `structuredClone`, `performance`, `TextEncoder`/`TextDecoder`, `atob`/`btoa`), `crypto` + `SubtleCrypto`, `WebSocket`, `XMLHttpRequest`, 이벤트 루프(`setTimeout`, `setInterval`, `queueMicrotask`, `AsyncContext`)를 제공합니다.

각 내장 기능은 클래스의 정적 `install()` 메서드입니다. `Engine`은 생성 시 의존성 순서대로 호출합니다. 각 기능은 프로토타입 체인을 정의하고, 메서드를 PHP 클로저로 감싼 `JsFunction` 인스턴스로 설치하며, 생성자를 전역으로 노출합니다.

## PHP↔JS 브리지

경계를 넘는 값 변환은 두 곳에서 처리됩니다:

- **`Phasis\Interop\PhpToJs::convert()`** — PHP 값을 `JsValue`로 변환합니다. `setGlobal()`, `call()` 인수, 호스트 함수 반환값에 사용됩니다. PHP 호출 가능 객체는 본문에서 이를 호출하는 `JsFunction`가 됩니다.
- **`PhpToJs::toPhp()`** — 호스트 함수 인수를 위해 `JsValue`를 PHP로 변환합니다.
- **`Engine::toPhp()`**(비공개) — `eval()` 및 `call()`의 결과를 위해 `JsValue`을 PHP로 변환합니다.

객체 브리지는 없습니다. 호출 가능 객체가 아닌 PHP 객체는 `undefined`로 변환됩니다([이슈 #18](https://github.com/inline0/phasis/issues/18)). `src/Bytecode/`의 `JsToPhp` 클래스는 상호 운용과 관련이 없으며, 아래에서 설명하는 트랜스파일러입니다. 변환 규칙은 [값 변환](https://phasis.inline0.dev/ko/docs/interop/values)에 설명되어 있습니다.

## 오류 처리

처리되지 않은 JS `throw`는 PHP `Phasis\Exceptions\JsThrowable`가 됩니다. 엔진에서 직접 발생하는 오류는 모두 `Phasis\Exceptions\RuntimeError`의 하위 클래스인 `Phasis\Exceptions\TypeError`, `RangeError` 또는 `ReferenceError`으로 표시됩니다. 엔진 내부에서는 JS `try` / `catch`가 `JsThrowable`, 모든 `RuntimeError`, `Phasis\Exceptions\SyntaxError`을 처리합니다. 호스트 함수에서 발생한 예외를 포함한 그 밖의 PHP 예외는 변환되지 않고 PHP 호출자에게 그대로 전파됩니다([이슈 #20](https://github.com/inline0/phasis/issues/20)).

제한은 두 가지입니다. 호출 스택 한도(`CallStack::maxDepth`, 4,096 프레임과 트랜스파일된 클로저용 8,192 프레임 가드)와 `maxLoopIterations`입니다. `Engine::setLimit()`를 통해 설정하며 트리 워커만 적용합니다([이슈 #19](https://github.com/inline0/phasis/issues/19)). 둘 다 `Phasis\Exceptions\InternalError`를 발생시킵니다. 문자열 길이, 출력 크기 또는 실행 시간 제한은 없습니다.

## 바이트코드 VM

핫 함수는 두 가지 방식으로 변환됩니다. `JsToPhp`(`src/Bytecode/` 내)은 숫자 연산 또는 로컬 변수 사용이 많은 본문에 PHP 클로저를 생성하며, PHP의 추적 JIT는 이를 네이티브 코드로 컴파일할 수 있습니다. 파이프라인은 `new Ctor(args)`, `obj.method(args)`, `this.member` 읽기를 변환하므로, 일반적인 루프 안의 클래스 메서드 패턴은 VM을 완전히 우회할 수 있습니다. 나머지는 모두 평면 바이트코드로 컴파일되어 `VM::execute`에서 처리됩니다. 사용자 지정 호출 스택은 모든 JS 호출의 상태를 단일 PHP 프레임에 유지하며, 프로토타입 메서드 읽기용 PC별 인라인 캐시를 사용합니다. 간단한 제너레이터 본문도 바이트코드로 컴파일되며, `Op::YIELD`은 PHP Fiber를 거치는 대신 힙 프레임 스냅샷을 캡처합니다.

컴파일러가 변환하지 않는 형태(e.g. `with`, 다시 바인딩하는 catch가 있는 `try`, `yield*`, 비동기 제너레이터)는 트리 워커로 대체 처리됩니다. 전체 메커니즘은 [바이트코드 VM](https://phasis.inline0.dev/ko/docs/advanced/bytecode-vm)을 참조하세요.
