3. 실행기 및 런타임
하자(Haja) 언어의 실행기(Interpreter) 동작 방식과 런타임 에러 명세입니다.
하자(Haja) 인터프리터 및 런타임 구현 가이드라인
이 문서는 ‘하자(Haja)’ 프로그래밍 언어의 런타임(인터프리터, VM, 컴파일러 백엔드 등)을 구현하고자 하는 개발자분들을 위한 언어 독립적(Language-Agnostic) 아키텍처 및 동작 명세서예요. 어떤 언어로 구현하시더라도 이 명세의 규칙을 엄격하게 준수해 주셔야 해요.
1. 런타임 아키텍처 및 환경 (Environment)
런타임은 AST를 순회하며 상태를 변경해요. 변수의 상태와 유효 범위는 Environment 자료구조로 관리한답니다.
1.1 어휘적 스코프와 클로저 (Lexical Scoping & Closures)
- 클로저 체인: 블록, 함수, 루프 진입 시 현재
Environment를 부모(parent)로 가지는 새로운 자식Environment를 생성해야 해요. - 일급 객체 함수 (First-class Function): 함수는 런타임에 호출 가능한(Invokable) 객체로 평가되어 변수에 담거나 파라미터로 전달할 수 있어요. 이때 함수 객체는 자신이 정의된 시점의
Environment를 포획(Capture)하여 클로저를 형성해야 한답니다. - 특수 식별자
바깥(OuterReference):'바깥의 이름'형태로 명시된 경우, 런타임은 현재 환경(Scope)을 제외하고 부모 환경부터 가장 가까운 선언을 탐색하여 해당 변수를 참조하거나 수정해야 해요.
1.2 함수 매개변수 (Parameters) 및 기본값 평가
함수 호출 시 런타임은 다음과 같이 인자(Arguments)를 파라미터 환경 변수에 바인딩해야 해요:
- 인자 부족 시: 생략된 파라미터 중
default노드(표현식)가 존재하는 파라미터는 해당 기본값을 평가하여 채워 넣어요. - 필수 매개변수 누락 에러: 만약 생략된 파라미터에
default필드가 존재하지 않는다면(필수 매개변수), 런타임은 인수를Null로 조용히 채우지 말고 즉시MissingArgumentError(또는ArgumentError)를 발생시켜야 해요. - 인자 초과 에러 (Extra Arguments): 전달된 인자의 개수가 선언된 파라미터의 개수보다 많을 경우, 초과된 인자를 조용히 무시해서는 안 되며 즉시
ArgumentError(또는TooManyArgumentsError)를 발생시켜 엄격하게 방어해야 한답니다.
2. 엄격한 타당성 및 타입 검증 (Strict Validations)
2.1 상수 불변성 검증 (Constant Enforcement)
- 변수 선언 시 AST의
isConst: true(고정하자)인 경우, 런타임은 해당 환경 맵에 식별자를 ’상수’로 기록해야 해요. - 모든 상태 변경 연산:
Assignment(재대입) 뿐만 아니라MathAdd,MathSubtract,ListAppend등 변수의 값을 변경하는 모든 연산 노드 평가 시 타겟 변수가 상수로 기록되어 있다면ConstantAssignmentError를 발생시켜야 한답니다.
2.2 동적 타입 체크 (Runtime Type Checking)
- 선언 및 상태 변경 검사: 변수 할당 시
typeAnnotation이 존재한다면, 들어오는value의 실제 런타임 타입을 검사해야 해요. 변수가 한 번 특정 타입으로 선언되면, 재대입이나 복합 대입(MathAdd등)을 통한 상태 변경 시에도 최초 선언된 타입 제약이 계속 강제되어야 한답니다. Null(비어있음)예외 허용: 하자의 모든 타입은 Nullable-by-default 철학을 따라요. 따라서 어떤 타입으로 엄격하게 선언된 변수라 할지라도, 재대입되는value가Null(비어있음)이라면 타입 검사기는 이를 호환되는 것으로 간주하여 무조건 통과시켜야 해요.[아무거나](Any Type): 타입이 생략되거나[아무거나]로 지정된 경우, 런타임 타입 검사기는 모든 제약을 무시하고 통과시켜야 한답니다.- 묵시적 형변환 금지 (No Implicit Casting): 문자열과 숫자를 더하는 등(
"1" + 2)의 연산에서 런타임은 암묵적 형변환을 절대 수행해서는 안 되며 즉시TypeError를 던져야 해요. 형변환은 오직 명시적인 내장 함수(<숫자로>(),<문자로>())를 통해서만 이루어져야 합니다.- 예외 (InputStatement): 단,
'변수'를 [타입]으로 입력받자구문의 경우,[타입]으로자체가 명시적 형변환(Explicit Cast) 명령으로 간주돼요. 따라서 표준 입력(문자열)을 지정된 타입으로 변환하는 것은 묵시적 형변환 금지 규칙을 위배하지 않는답니다.
- 예외 (InputStatement): 단,
2.3 Nullable과 기본 반환값
- 선언만 된 변수 (Nullable):
isDeclarationOnly: true(준비하자)로 선언된 변수는 런타임 메모리에 즉시Null(비어있음) 값으로 초기화되어야 해요. 즉, 하자의 모든 타입은 기본적으로 Null을 허용(Nullable)한답니다. - 기본 반환값 (Default Return): 함수나 메서드가
ReturnStatement(돌려주자)를 만나지 않고 블록 실행을 끝마치면, 런타임은 호출자에게 자동으로Null을 반환해야 해요.
2.4 널 참조 안전성 (Null Safety)
- 런타임 평가 중
Null(비어있음)값에 대해 속성/인덱스를 접근하려 하거나(MemberExpression), 함수로 호출(CallExpression)하려 할 경우 런타임은 즉시NullReferenceError를 발생시켜야 해요. - 연산자 널 안전성 및 예외:
MathAdd,MathSubtract,ListAppend, 그리고BinaryExpression(산술, 대소 비교 등)의 피연산자 중 하나라도Null인 경우, 런타임은 즉시NullReferenceError(또는TypeError)를 발생시켜 시스템 크래시를 방지해야 한답니다.- 단, 동등 비교(
같다,다르다)는 예외예요. 널 가드(Null Guard) 패턴의 구현을 위해, 동등 비교 연산자 수행 시 한쪽 피연산자가Null이더라도 에러를 발생시키지 않고 정상적으로 불리언(참/거짓) 값을 평가하여 반환해야 해요.
- 단, 동등 비교(
3. 객체지향 시스템 (OOP) 상세
3.1 인스턴스화 및 생성자 규칙 (Instantiation & Constructors)
- 인터페이스 직접 생성 금지:
NewExpression의 대상이 인터페이스(순수 규약)일 경우, 인스턴스화할 수 없으므로 즉시InterfaceInstantiationError를 발생시켜야 해요. - 메모리 할당: 유효한 클래스의
NewExpression평가 시 독립된 인스턴스 객체를 생성해요. - 부모 속성 상속:
baseClass(부모 클래스)가 존재한다면 부모의 속성과 메서드를 현재 객체로 복사 또는 상속 체인(MRO)에 연결해야 한답니다. - 생성자 연쇄 호출 규칙:
- 자식 클래스에 생성자(
<처음 만들어질 때>)가 선언되어 있지 않다면, 런타임은 부모 클래스의 생성자를 자동으로 찾아서 호출해야 해요. - 자식 클래스에 생성자가 오버라이딩(덮어쓰기)되어 있다면, 런타임은 부모 생성자를 자동으로 호출하지 않아요. 개발자가 명시적으로
부모의 <처음 만들어질 때>()를 호출해야 한답니다.
- 자식 클래스에 생성자(
3.2 접근 제한자 검증 (Access Modifier Check)
private(숨기자): 객체 외부에서의 호출(CallExpression/MemberExpression)을 엄격히 차단해요. 오직 해당 객체의 클래스 내부에 정의된 메서드에서나의 <메서드>로 호출할 때만 허용된답니다. (위반 시AccessViolationError)protected(물려주자): 해당 객체의 클래스와 상속받은 자식 클래스 내부에서만 호출을 허용해요.
3.3 특수 바인딩 나 (this) 와 부모 (super)
나(this): 메서드 실행 시 생성되는 로컬 Environment에는 항상 호출 주체 객체가나로 바인딩되어야 해요.부모(super):SuperReference평가 시,나객체의 직계 부모 클래스 문맥을 반환하여 오버라이딩 되기 전의 원본 메서드를 호출할 수 있게 해 준답니다.
3.4 상향 형변환 (Upcasting)과 인터페이스 (Interface)
- Upcasting: 자식 객체를 부모 타입의 매개변수나 변수에 할당하는 것은 언제나 안전한 호환으로 간주되어 타입 검사를 통과해야 해요.
- Interface 검증: 인터페이스는 런타임에 인스턴스화될 수 없는 순수 규약이랍니다.
- 인터페이스 타입을 인자로 받는 함수에 객체를 넘길 때, 런타임은 해당 객체의 클래스가 해당 인터페이스를
따른다(구현체)고 선언했는지 검증해야 해요. - 클래스 선언 단계(평가 단계)에서, 인터페이스를 따른다고 명시한 클래스가 인터페이스의 필수 메서드 원형들을 모두 구현하지 않았다면 런타임(또는 컴파일러)은 즉시
InterfaceImplementationError를 던져야 한답니다.
- 인터페이스 타입을 인자로 받는 함수에 객체를 넘길 때, 런타임은 해당 객체의 클래스가 해당 인터페이스를
3.5 심화 객체지향 기능 및 제약 사항
- 정적 멤버 (Static Members): 현재 버전에서는 클래스 내 정적(Static) 속성 및 메서드 선언을 지원하지 않아요. 전역 상태나 공통 유틸리티는 모듈(파일) 단위의 일반 변수와 함수를 통해 구현해야 해요.
- 추상 클래스 (Abstract Classes): 순수 규약은 인터페이스(
규정하자)로, 구현체는 일반 클래스(설계하자)로 분리하여 정의해요. 구현부가 섞여 있는 추상 클래스 형태는 별도로 지원하지 않는답니다. - 오버로딩 (Overloading): 메서드와 생성자의 다중 선언(오버로딩)은 지원하지 않아요. 동일한 이름의 함수나 메서드가 중복 선언될 경우 런타임은
DuplicateDeclarationError를 발생시켜야 해요. - 객체 동등 비교 (Equality): 사용자 정의 객체에 대한
같다(==),다르다(!=) 연산은 객체의 내부 속성값이 아닌 **참조(Reference, 동일 메모리 주소 여부)**를 기준으로 비교한답니다. - 타입 검사 연산 (
일종이다): 런타임에 특정 객체가 특정 클래스나 인터페이스 타입인지 확인하기 위해instanceof역할인A 가 B 의 일종이다연산을 지원해요. 상속 계층과 인터페이스 구현 여부를 모두 확인하여참/거짓을 반환해야 한답니다.
4. 제어 흐름 (Control Flow) 및 예외 처리
4.1 스위치문 관통 (Fallthrough) 및 기본(Default) 검증
- 스위치 분기 내에서
FallthroughStatement(다음으로 이어가자)를 만나면, break 깃발을 무시하고 소스코드 상 바로 밑에 선언된 다음case블록을 조건(테스트) 평가 없이 즉시 연속해서 실행해야 해요. - 관통 및 누락 검증 (Unhandled Switch): 마지막
case블록에서다음으로 이어가자를 호출하거나, 애초에 들어온 값이 일치하는case가 전혀 없을 때, 런타임은default(나머지는:) 블록을 찾아 실행해야 한답니다. 만약default블록이 존재하지 않는다면, 조용히 넘어가서는 안 되며 즉시UnhandledSwitchCaseError를 던져 모든 스위치 분기가 안전하게 처리되도록 강제해야 해요.
4.2 예외 처리 (Exception / Try-Catch-Finally)
- 런타임은 예외 발생 시 콜스택을 즉시 풀기(Unwinding) 시작해야 해요.
TryStatement(일단 해보자) 블록을 실행하던 중 에러가 발생하면 런타임은 이를 가로채어CatchClause(오류가 발생했다면)의 파라미터 환경 변수에 에러 객체를 바인딩하고 해당 블록을 실행한답니다.CatchClause부재 시 (Try-Finally): 만약 오류 처리 블록(handler)이 생략되어 있다면, 런타임은 에러를 무시해서는 안 되며,FinallyClause가 끝난 직후 상위 호출 스택으로 해당 에러를 반드시 다시 던져야(Re-throw) 해요.
FinallyClause(마무리는 항상)가 존재한다면, 에러가 발생했든 정상 종료되었든, 심지어 내부에ReturnStatement가 있었더라도 반드시 콜스택을 빠져나가기 직전에 해당 블록을 보장 실행해야 한답니다.
4.3 모듈과 임포트 (Module / Import)
ImportStatement평가 시, 런타임은 지정된 외부 파일이나 내장 라이브러리의 코드를 평가하여 독립된 Environment에 적재해야 해요.- 중복 및 순환 방지 (Circular Import): 모듈 로더는 반드시 캐시 메커니즘을 사용하여 동일한 모듈이 두 번 평가되지 않도록 해야 한답니다. 만약 모듈 A가 평가 도중 모듈 B를 부르고, B가 다시 A를 부르는 순환 참조가 발생하면, 런타임은 무한 루프에 빠지지 않고 해당 시점까지 평가 완료된 A의 부분 상태(Partial Environment/Object)를 즉시 반환하여 B의 평가를 계속 진행해야 해요.
- 임포트된 식별자들은 현재 파일의 전역 Environment에 병합되어 즉시 사용할 수 있어야 한답니다.
4.4 반복문 제어 (Loop Control)
BreakStatement(반복을 끝내자): 실행 중 이 구문을 만나면, 런타임은 가장 안쪽(Inner-most)에 있는 반복문(WhileLoop,ForEachLoop,ForRangeStatement)의 제어 흐름을 즉시 빠져나와야 해요.- 주의: 하자(Haja)의 스위치(
SwitchStatement)는 각 case가 끝나면 자동으로 블록을 빠져나오도록 설계되었으므로, 스위치문 내부에서BreakStatement를 사용하면 스위치를 무시하고 그 바깥을 감싸고 있는 반복문을 탈출하게 된답니다. IllegalBreakError: 만약 반복문 내부가 아닌 최상위 레벨이나 단순 함수 블록 안에서BreakStatement가 실행될 경우, 런타임은 잡히지 않은(Uncaught) 루프 탈출로 간주하여 즉시IllegalBreakError(또는SyntaxError)를 발생시켜야 해요.
- 주의: 하자(Haja)의 스위치(
ForRangeStatement평가 방향:1부터 10까지와 같이 숫자 범위 반복문 평가 시, **종료값은 항상 포함(Inclusive)**돼요 (예: 1부터 3까지는 1, 2, 3 실행).- 만약 시작값이 종료값보다 크다면 (예:
10부터 1까지), 런타임은 즉시 종료하지 않고 **역순(-1씩 차감)**으로 반복문을 실행해야 한답니다.
- 만약 시작값이 종료값보다 크다면 (예:
5. 내장 리터럴 및 연산자 (Built-in Literals & Operators)
5.1 목록과 사전
- 목록 (ListLiteral): 런타임 평가 시 크기가 동적으로 조절되는 배열(Dynamic Array) 객체로 생성되어야 해요.
- 인덱스 접근 (
1번째 값): 런타임은 인간 친화적인 1-based 인덱싱을 사용한답니다 (예:1번째는 배열의 첫 번째 요소).- 오프셋 변환: 컴파일러나 인터프리터가 내부적으로 0-based 인덱스를 사용하는 호스트 언어(C, Rust, Python 등)로 작성되었다면, 런타임 속성 평가 시 반드시
접근할 인덱스 값 - 1로 오프셋을 변환하여 처리해야 해요. - 경계 검사 (Bounds Check): 만약 배열의 길이를 벗어난 인덱스에 접근하거나 할당하려 할 경우, 조용히 무시하지 않고 즉시
IndexOutOfBoundsError를 발생시켜야 한답니다.
- 오프셋 변환: 컴파일러나 인터프리터가 내부적으로 0-based 인덱스를 사용하는 호스트 언어(C, Rust, Python 등)로 작성되었다면, 런타임 속성 평가 시 반드시
- 길이 조회 (
길이):'배열'의 길이평가 시 배열의 현재 크기를 정수로 반환해야 해요.
- 인덱스 접근 (
- 사전 (DictLiteral): 런타임 평가 시 문자열 등 해시 가능한 키를 값에 매핑하는 해시 테이블(Hash Map / Dictionary) 객체로 생성되어야 한답니다.
- 키 접근: 사전 객체에 존재하지 않는 키를 조회하려 할 경우, 즉시
KeyError를 발생시켜야 해요.
- 키 접근: 사전 객체에 존재하지 않는 키를 조회하려 할 경우, 즉시
5.2 논리 연산의 단락 평가 (Short-circuit Evaluation)
그리고(Logical AND):좌항 그리고 우항평가 시, 런타임은 좌항을 먼저 평가하며 그 결과가거짓(False)이라면 우항을 절대 평가하지 않고 즉시거짓을 반환해야 해요. (우항의 함수 호출이나 런타임 에러는 발생하지 않는답니다.)또는(Logical OR):좌항 또는 우항평가 시, 런타임은 좌항을 먼저 평가하며 그 결과가참(True)이라면 우항을 절대 평가하지 않고 즉시참을 반환해야 해요.
5.3 수학 연산 예외 (DivideByZeroError)
- 0으로 나누기: 나눗셈(
/) 연산자 평가 시, 우항(분모)의 런타임 값이 0(0,0.0)인 경우 런타임은 호스트 시스템의 크래시나무한대(Infinity)/NaN반환을 허용하지 않고, 즉시MathError(또는DivideByZeroError) 예외를 발생시켜 콜스택을 풀어야 한답니다.
6. 표준 입출력 (Standard I/O)
6.1 출력 (Print / PrintInline)
PrintStatement(출력하자): 런타임은 평가된 값을 문자열로 변환하여 호스트의 표준 출력 스트림(또는 UI 콘솔)에 쓰고, 마지막에 반드시 줄바꿈(Newline,\n)을 추가한 뒤 버퍼를 비워야(Flush) 해요.PrintInlineStatement(이어출력하자): 런타임은 값을 표준 출력에 쓰되, 줄바꿈 기호를 추가하지 않아요. 단, 입력 프롬프트 등을 위해 사용될 수 있으므로 반드시 즉시 출력 버퍼를 비워(Flush) 화면에 표시되도록 보장해야 한답니다.
6.2 입력 (Input)
- 추상화된 입력 소스 (I/O Provider):
InputStatement(입력받자) 실행 시, 런타임은 호스트 환경의 표준 입력(stdin), 브라우저의prompt(), 혹은 GUI 입력 다이얼로그 등 환경에 맞는 동기적 입력 스트림을 대기(Blocking)해야 해요. - 허용되는 타입 제약: 입력 시 지정할 수 있는 타입(
typeAnnotation)은 반드시 원시 타입([문자열],[숫자],[논리])으로 제한되어야 한답니다. 만약[자동차]와 같은 커스텀 클래스나 복합 타입을 지정할 경우, 런타임은 즉시UnsupportedInputTypeError를 발생시켜야 해요. - 기본 타입 (Default String): 타입이 생략된 구문(
'변수'를 입력받자)이 평가될 경우, 기본적으로[문자열]타입으로 취급해요. - 엄격한 변환 에러 (Strict Conversion): 문자열로 들어온 입력값을 지정된 타입에 맞게 파싱해요. 만약 지정된 타입으로 변환할 수 없는 값(예:
[숫자]를 기대했는데"가나다"입력)이라면, 런타임은 암묵적 처리나 임의의 재입력 루프를 돌리지 말고 즉시InputConversionError를 던져야 한답니다. 실패 처리는 언어 내부의일단 해보자(Try-Catch) 구문을 통해 개발자가 직접 제어하도록 강제해요.