3. 실행기 및 런타임
하리(Hari) 언어의 실행기(Interpreter) 동작 방식과 런타임 에러 명세입니다.
이 문서는 ‘하리(Hari)’ 프로그래밍 언어의 런타임(인터프리터, 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)를 파라미터 환경 변수에 바인딩해야 해요:
- 인자 전달 방식 (Call by Object Reference): 함수에 인자를 전달할 때, 런타임은 객체의 전체 복사본이 아닌 객체의 메모리 참조(Reference)를 파라미터 환경 변수에 바인딩해요.
- 가변(Mutable) 객체인 목록(List)이나 사전을 인수로 받아 함수 내부에서 그 속성을 수정(
'인수'의 1번째를 "값"으로 정하자)하면, 원본 객체도 함께 수정된답니다. - 하지만 문자열(String)이나 숫자 같은 불변(Immutable) 객체는 속성을 수정할 수 없어요. 그래서 파라미터 변수 자체에 새로운 값을 아예 재대입(
'인수'를 "새로운값"으로 정하자)하더라도 이는 로컬 환경의 연결(Binding)만 바꿀 뿐 바깥쪽 원본 변수에는 아무런 영향을 주지 않아요.
- 가변(Mutable) 객체인 목록(List)이나 사전을 인수로 받아 함수 내부에서 그 속성을 수정(
- 인자 부족 시: 생략된 파라미터 중
default노드(표현식)가 존재하는 파라미터는 해당 기본값을 평가하여 채워 넣어 주세요. - 필수 매개변수 누락 에러: 만약 생략된 파라미터에
default필드가 존재하지 않는다면(필수 매개변수), 런타임은 인수를Null로 조용히 채우지 말고 즉시MissingArgumentError(또는ArgumentError)를 발생시켜야 해요. - 인자 초과 에러 (Extra Arguments): 전달된 인자의 개수가 선언된 파라미터의 개수보다 많을 경우, 초과된 인자를 조용히 무시해서는 안 되며 즉시
ArgumentError(또는TooManyArgumentsError)를 발생시켜 엄격하게 방어해 주세요.
2. 엄격한 타당성 및 타입 검증 (Strict Validations)
2.1 상수 불변성 검증 (Constant Enforcement)
- 변수를 선언할 때 AST의
isConst: true(고정하자)인 경우, 런타임은 해당 환경 맵에 식별자를 ’상수’로 기록해야 해요. - 모든 상태 변경 연산:
VariableDeclaration(단, 환경에 이미 식별자가 존재하는 재대입의 경우) 뿐만 아니라CompoundAssignment(복합 대입),ListPushStatement(목록 추가) 등 변수나 객체의 값을 변경하는 모든 연산 노드를 평가할 때, 타겟 변수가 상수로 기록되어 있다면ConstantAssignmentError를 발생시켜야 한답니다.
2.2 동적 타입 체크 (Runtime Type Checking)
- 변수 선언과 재대입의 단일 평가: 파서는 변수 선언과 재대입을 모두
VariableDeclaration노드로 통합하여 생성해요. 런타임은 이 노드를 평가할 때, 해당 식별자가 현재 스코프(환경)에 이미 존재하는지 검사해야 한답니다. 존재하지 않는다면 새로운 변수로 선언하고, 존재한다면 기존 변수의 값을 갱신(재대입)해 주세요. - 선언 및 상태 변경 검사: 변수 할당 시
typeAnnotation이 존재한다면, 들어오는value의 실제 런타임 타입을 검사해야 해요. 변수가 한 번 특정 타입으로 선언되면, 재대입이나 복합 대입(CompoundAssignment)을 통한 상태 변경 시에도 최초 선언된 타입 제약이 계속 강제되어야 해요. - 제네릭(Generics) 타입 검사:
typeAnnotation에 제네릭 인자(예:[(숫자)목록])가 포함되어 있다면, 런타임은 해당 변수에 값이 추가되거나 대입될 때 내부 요소의 타입까지 재귀적으로 검사(Type Reification)하여 안전성을 보장해야 해요. 위반 시에는TypeError를 발생시켜 주세요. Null(비어있음)예외 허용: 하리의 모든 타입은 Nullable-by-default 철학을 따른답니다. 따라서 어떤 타입으로 엄격하게 선언된 변수라 할지라도, 재대입되는value가Null(비어있음)이라면 타입 검사기는 이를 호환되는 것으로 간주하여 무조건 통과시켜야 해요.- 함수 반환 타입 검사: 함수 선언에 반환 타입(
FunctionDeclaration.returnType,[숫자]를 돌려주는)이 있으면, 런타임은 함수가 돌려준 값을 그 타입으로 검사하고 맞지 않으면TypeError를 발생시켜야 해요. 반환 타입은 필수가 아니어서 생략하면 아무 값이나 돌려줄 수 있고,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를 발생시켜야 해요. - 연산자 널 안전성 및 예외:
CompoundAssignment,ListPushStatement, 그리고BinaryExpression(산술, 대소 비교 등)의 피연산자 중 하나라도Null인 경우, 런타임은 즉시NullReferenceError(또는TypeError)를 발생시켜 시스템 크래시를 방지해 주셔야 해요.- 단, 동등 비교(
같다,다르다)는 예외랍니다. 널 가드(Null Guard) 패턴의 구현을 위해, 동등 비교 연산자를 수행할 때 한쪽 피연산자가Null이더라도 에러를 발생시키지 않고 정상적으로 불리언(참/거짓) 값을 평가하여 반환해야 해요.
- 단, 동등 비교(
3. 객체지향 시스템 (OOP) 상세
3.1 인스턴스화 및 생성자 규칙 (Instantiation & Constructors)
- 추상 타입 직접 생성 금지:
NewExpression의 대상이 인터페이스(규정하자)나 추상 클래스(밑설계하자)일 경우, 인스턴스화할 수 없으므로 즉시InstantiationError를 발생시켜야 해요. - 메모리 할당: 유효한 클래스의
NewExpression평가 시 독립된 인스턴스 객체를 생성해 주세요. - 부모 속성 상속:
baseClass(부모 클래스)가 존재한다면 부모의 속성과 메서드를 현재 객체로 복사하거나 상속 체인(MRO)에 연결해야 한답니다. - 생성자 연쇄 호출 규칙:
- 자식 클래스에 생성자(
<처음 만들어질 때>)가 선언되어 있지 않다면, 런타임은 부모 클래스의 생성자를 자동으로 찾아서 호출해야 해요. - 자식 클래스에 생성자가 오버라이딩(덮어쓰기)되어 있다면, 런타임은 부모 생성자를 자동으로 호출하지 않아요. 개발자가 명시적으로
부모의 <처음 만들어질 때>()를 호출해 주어야 한답니다.
- 자식 클래스에 생성자(
3.2 접근 제한자 검증 (Access Modifier Check)
private(숨기자): 객체 외부에서의 호출(CallExpression/MemberExpression)을 엄격히 차단해요. 오직 해당 객체의 클래스 내부에 정의된 메서드에서나의 <메서드>로 호출할 때만 허용된답니다. (위반 시에는AccessViolationError를 발생시켜 주세요.)protected(물려주자): 해당 객체의 클래스와 상속받은 자식 클래스 내부에서만 호출을 허용해요.
3.3 특수 바인딩 나 (this), 부모 (super), 우리 (static)
나(this): 인스턴스 메서드를 실행할 때 생성되는 로컬 Environment에는 항상 호출 주체 인스턴스 객체가나로 바인딩되어야 해요.부모(super):SuperReference를 평가할 때,나객체의 직계 부모 클래스 문맥을 반환하여 오버라이딩 되기 전의 원본 메서드를 호출할 수 있게 해 주세요.우리(static): AST에isStatic: true로 선언된 멤버는 인스턴스가 아닌 클래스 자체에 귀속된답니다. 클래스 내부에서는'우리'키워드를 통해 접근하며, 외부에서는클래스명을 통해 직접 접근해요. 정적 메서드 내에서는 인스턴스(나)를 참조할 수 없으며, 시도할 경우StaticContextError를 던져 주셔야 해요.
3.4 상향 형변환 (Upcasting)과 인터페이스 (Interface)
- Upcasting: 자식 객체를 부모 타입의 매개변수나 변수에 할당하는 것은 언제나 안전한 호환으로 간주되어 타입 검사를 통과해야 해요.
- Interface 및 Abstract 검증: 인터페이스는 런타임에 인스턴스화될 수 없는 순수 규약이에요.
- 인터페이스 타입을 인자로 받는 함수에 객체를 넘길 때, 런타임은 해당 객체의 클래스가 해당 인터페이스를
따른다(구현체)고 선언했는지 검증해야 한답니다. - 클래스 선언 단계(평가 단계)에서, 인터페이스를 따른다고 명시한 클래스가 인터페이스의 필수 메서드 원형들을 모두 구현하지 않았다면 런타임(또는 컴파일러)은 즉시
InterfaceImplementationError를 던져야 해요. (추상 클래스 상속 시의 미구현 추상 메서드 검증도 이와 동일하게 처리해 주세요.)
- 인터페이스 타입을 인자로 받는 함수에 객체를 넘길 때, 런타임은 해당 객체의 클래스가 해당 인터페이스를
3.5 심화 객체지향 기능
- 추상 클래스 (Abstract Classes):
isAbstract: true(밑설계하자)로 선언된 클래스는 인스턴스화할 수 없으며, 내부에 몸체가 없는 추상 메서드(<메서드>가 있어야 한다 ())를 포함할 수 있어요. 상속받은 자식 클래스는 반드시 이 추상 메서드들을 오버라이딩해야 한답니다. - 오버로딩 (Overloading): 메서드와 생성자의 다중 선언을 허용해요. 런타임은 호출 시 제공된 인자의 개수와 런타임 타입을 분석하여, 시그니처가 가장 일치하는 메서드를 동적으로 찾아(Dynamic Dispatch) 실행해야 해요. 일치하는 시그니처가 없거나 모호할 경우에는
MethodSignatureNotFoundError를 발생시켜 주세요. - 연산자 오버로딩 (Operator Overloading): 클래스 내부에 특별한 매직 메서드(예:
<기호 같다>,<기호 더하기>)를 선언할 수 있어요. 런타임은A와 B가 같다(==)나A와 B를 더하자(+)를 평가할 때, 좌항 객체에 대응하는<기호 ...>메서드가 존재하면 원시 연산을 수행하는 대신 해당 메서드를 호출하여 그 반환값을 연산 결과로 사용해야 해요. - 동적 리플렉션 (Dynamic Reflection): 메서드 호출 시 정적 이름(
<메서드명>) 대신 변수나 식(<"문자열">또는<'변수'>)이 꺾쇠 안에 주어지면, 런타임은 해당 식을 문자열로 평가한 뒤 객체 내에서 그 문자열과 일치하는 이름의 메서드를 동적으로 탐색(Dynamic Dispatch)하여 실행해야 해요. 메서드를 찾을 수 없으면MethodNotFoundError를 발생시켜 주시면 됩니다. - 타입 검사 연산 (
일종이다): 런타임에 특정 객체가 특정 클래스나 인터페이스 타입인지 확인하기 위해instanceof역할인A 가 B 의 일종이다연산을 지원해요. 상속 계층과 인터페이스 구현 여부를 모두 확인하여참/거짓을 반환해 주셔야 해요. - 속성 접근자 (Property Getters/Setters): 변수를 선언할 때
getter나setter블록이 존재할 경우, 해당 속성을 읽을 때는 내부 변수를 그대로 반환하는 대신getter블록을 평가하여 그 결과값을 반환해야 해요. 반대로 값을 할당할 때는setter.param식별자에 할당할 값을 바인딩한 후setter.body블록을 실행하여 로직을 처리해 주세요.
4. 제어 흐름 (Control Flow) 및 예외 처리
4.1 스위치문 관통 (Fallthrough) 및 기본(Default) 검증
- 스위치 분기 내에서
FallthroughStatement(다음으로 이어가자)를 만나면, break 깃발을 무시하고 소스코드 상 바로 밑에 선언된 다음case블록을 조건(테스트) 평가 없이 즉시 연속해서 실행해야 해요. - 일치하는
case가 없을 때 (Default): 들어온 값과 일치하는case가 전혀 없으면, 런타임은default(나머지는:) 블록이 있을 경우 그 블록을 실행해요.default블록이 없으면 아무것도 실행하지 않고 다음 문장으로 넘어가요(오류가 아니에요).
4.2 예외 처리 (Exception / Try-Catch-Finally)
- 런타임은 예외가 발생하면 콜스택을 즉시 풀기(Unwinding) 시작해야 해요.
TryStatement(일단 해보자) 블록을 실행하던 중 에러가 발생하면 런타임은 등록된handlers(CatchClause배열)를 선언된 순서대로 순회한답니다.- 특정 예외 필터링: 발생한 에러 객체의 타입이
CatchClause의catchType과 일치(업캐스팅 호환 포함)하거나,catchType이null(오류가 발생했다면)인 경우 해당 블록의 파라미터에 에러를 바인딩하고 실행을 재개해요. 매칭된 첫 번째 블록만 실행하고 나머지는 무시해 주시면 돼요. - CatchClause 부재 또는 미스매치: 만약 오류 처리 블록(
handlers)이 아예 없거나, 발생한 오류와 일치하는 타입의 블록이 하나도 없다면, 런타임은 에러를 무시해서는 안 되며FinallyClause가 끝난 직후 상위 호출 스택으로 해당 에러를 반드시 다시 던져야(Re-throw) 해요.
- 특정 예외 필터링: 발생한 에러 객체의 타입이
FinallyClause(마무리는 항상)가 존재한다면, 에러가 발생했든 정상 종료되었든, 심지어 내부에ReturnStatement가 있었더라도 반드시 콜스택을 빠져나가기 직전에 해당 블록을 보장 실행해 주셔야 한답니다.
4.3 모듈과 임포트 (Module / Import)
-
ImportStatement를 평가할 때, 런타임은 모듈의 표기를 보고 종류를 정해요. 문자열("파일")은 항상 프로젝트 상대 경로의 로컬 파일이고, 타입 리터럴([이름])은 표준 라이브러리, 런타임과 함께 배포하는 패키지, 또는 설치한 서드파티 패키지예요. 어느 쪽이든 코드를 평가하여 독립된 Environment에 적재해야 해요. -
중복 및 순환 방지 (Circular Import): 모듈 로더는 반드시 캐시 메커니즘을 사용하여 동일한 모듈이 두 번 평가되지 않도록 방지해 주세요. 만약 모듈 A가 평가 도중 모듈 B를 부르고, B가 다시 A를 부르는 순환 참조가 발생하면, 런타임은 무한 루프에 빠지지 않고 해당 시점까지 평가 완료된 A의 부분 상태(Partial Environment/Object)를 즉시 반환하여 B의 평가를 계속 진행해야 한답니다.
-
가져오기 병합: AST의
imports속성이null(전부 가져오기)이면 적재된 모듈의 모든 공개 식별자를 현재 파일의 전역 Environment에 병합해요. 만약 식별자 목록이 지정되어 있다면(["함수명", "변수명"]), 명시된 식별자만 정확히 선택하여 병합해야 하며 존재하지 않는 항목을 요청 시 즉시ImportError를 발생시켜 주세요. -
임포트 없이 쓰는 것과 표준 라이브러리: 임포트 없이 쓰는 것은 문법 동사(출력·입력 등)와 원시 타입 변환 함수(5.5)뿐이에요. 수학 같은 나머지 기능은 모두 표준 라이브러리(
std)이며"builtin"모듈로 임포트해야 해요. 표준 라이브러리의 모듈·함수 이름은 언어마다 달라요(하리의[수학]과<올림>, 카나데의【数学】와〈切り上げ〉). 구현은 언어 중립 ID(math.ceil)에 언어별 이름을 붙여 제공해 주세요. -
언어 결정: 임포트된 코드는 자기 언어로 실행해요. 로컬 파일(
"파일")은 확장자(.hr하자,.knd카나데)로 언어가 정해지므로 하리 파일과 카나데 파일이 서로를 가져올 수 있어요. 패키지 폴더는 임포트하는 쪽 언어의 진입점(<모듈>/<언어>/index.<확장자>)을 로드하고, 그 언어의 진입점이 없으면ImportError를 발생시켜 주세요. 임포트된 모듈도 자기 언어의 표준 라이브러리(5.5의 함수 포함)를 쓸 수 있어야 해요. -
모듈 범위 (Module Scope): 가져온 함수·메서드·생성자의 본문은 자기 모듈의 이름 공간에서 함수를 찾아야 해요. 같은 모듈이 선언한 다른 함수(도우미)와 그 모듈이 스스로 가져온 것(다른 패키지, 표준 라이브러리 함수)은 가져오는 쪽이 따로 가져오지 않아도 호출할 수 있고, 가져오는 쪽에 같은 이름의 함수가 있어도 서로 가려서는 안 돼요. 가져오는 쪽에서 보이는 것은
imports가 요청한 식별자뿐이에요. 모듈 최상위의 변수와 문장도 모듈의 것이에요. 모듈은 처음 가져올 때 한 번만 적재되어 최상위 코드가 한 번 실행되고(다시 가져와도 같은 상태), 가져온 함수는 그 변수를 읽고 바꿀 수 있어요. 가져오는 쪽의 변수는 모듈의 함수 안에서 보이지 않아요(ReferenceError). 모듈이 선언한 클래스와 인터페이스 중 가져오는 쪽에 같은 이름이 없는 것은 자기 이름으로 등록되어 모듈의 함수가 만들 수 있어요. 이미 같은 이름이 다른 모듈이나 프로그램 자신의 것으로 있다면ImportError를 발생시켜야 해요(가져올 때<이름>을 <별칭>으로로 다른 이름을 붙이면 피할 수 있어요). -
서드파티 패키지: 이름이 git 경로 꼴(
[github.com/주인/저장소])이면 패키지 매니저(hana add)가 프로젝트의hana.json(최소 버전)과hana-lock.json(고른 버전과 커밋)에 따라 내려받은 폴더를 가리켜요. 설치되어 있지 않으면ImportError(설치 안내)이고, 런타임은 실행 중에 패키지를 내려받지 않아요. -
파일 시스템이 없는 런타임: 브라우저 놀이터처럼 파일 시스템이 없는 환경은 표준 라이브러리 임포트만 지원하고, 파일·패키지 임포트는 오류로 알려 주세요. 존재하지 않는 모듈이나 항목은
ImportError예요.
4.4 반복문 제어 (Loop Control)
BreakStatement(반복을 끝내자): 실행 중 이 구문을 만나면, 런타임은 가장 안쪽(Inner-most)에 있는 반복문(WhileLoop,ForEachLoop,ForRangeStatement)의 제어 흐름을 즉시 빠져나와야 해요.- 주의: 하리(Hari)의 스위치(
SwitchStatement)는 각 case가 끝나면 자동으로 블록을 빠져나오도록 설계되었으므로, 스위치문 내부에서BreakStatement를 사용하면 스위치를 무시하고 그 바깥을 감싸고 있는 반복문을 탈출하게 된답니다. IllegalBreakError: 만약 반복문 내부가 아닌 최상위 레벨이나 단순 함수 블록 안에서BreakStatement가 실행될 경우, 런타임은 잡히지 않은(Uncaught) 루프 탈출로 간주하여 즉시IllegalBreakError(또는SyntaxError)를 발생시켜 주셔야 해요.
- 주의: 하리(Hari)의 스위치(
ForRangeStatement평가 방향:1부터 10까지와 같이 숫자 범위 반복문을 평가할 때, 종료값은 항상 포함(Inclusive)된답니다 (예: 1부터 3까지는 1, 2, 3 실행).- 만약 시작값이 종료값보다 크다면 (예:
10부터 1까지), 런타임은 즉시 종료하지 않고 역순(-1씩 차감)으로 반복문을 실행해야 해요.
- 만약 시작값이 종료값보다 크다면 (예:
4.5 호출 깊이 제한 (Call Depth)
- 함수·메서드 호출과 인스턴스 생성이 겹쳐 쌓이는 깊이에 한도(10000단계)를 두어야 해요. 한도를 넘으면 호스트의 콜스택이 터지도록 두지 말고, 프로그램이
일단 해보자로 잡을 수 있는RecursionError를 발생시켜 주세요.
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 등)로 작성되었다면, 런타임 속성을 평가할 때 반드시
- 길이 조회 (
길이):'목록'의 '길이'를 평가할 때 목록의 현재 크기를 정수로 반환해 주세요. - 네이티브 목록 조작 (추가/꺼내기): 객체지향 메서드 대신, 다음과 같은 언어 내이티브 구문을 통해 목록을 조작해요.
- 추가 (
ListPushStatement):'목록' (앞에/뒤에) 값을 추가하자평가 시, 목록 객체의 앞(0번 인덱스, Shift) 또는 뒤(마지막, Append)에 요소를 삽입해 주세요. - 꺼내기 (
ListPopStatement/ListPopExpression):'목록' (앞에서/뒤에서) 꺼내자(문장) 또는꺼낸 값(수식) 평가 시, 지정된 위치의 원소를 제거해요. (빈 목록일 경우에는 즉시IndexOutOfBoundsError를 발생시켜 주세요.)
- 추가 (
- 내장 메서드: 추가로 다음 메서드를 지원해요.
<비우기>(): 목록의 모든 원소를 제거하여 길이를 0으로 만들어 줍니다.
- 인덱스 접근 (
- 사전 (DictLiteral): 런타임 평가 시 문자열 등 해시 가능한 키를 값에 매핑하는 해시 테이블(Hash Map / Dictionary) 객체로 생성되어야 해요.
- 키 접근: 사전 객체에 존재하지 않는 키를 조회하려 할 경우, 즉시
KeyError를 발생시켜 주세요.
- 키 접근: 사전 객체에 존재하지 않는 키를 조회하려 할 경우, 즉시
5.2 논리 연산의 단락 평가 (Short-circuit Evaluation)
그리고(Logical AND):좌항 그리고 우항을 평가할 때, 런타임은 좌항을 먼저 평가하며 그 결과가거짓(False)이라면 우항을 절대 평가하지 않고 즉시거짓을 반환해야 해요. (우항의 함수 호출이나 런타임 에러는 발생하지 않아요.)또는(Logical OR):좌항 또는 우항을 평가할 때, 런타임은 좌항을 먼저 평가하며 그 결과가참(True)이라면 우항을 절대 평가하지 않고 즉시참을 반환해야 한답니다.- 조건의 타입:
만약과동안 반복의 조건, 그리고그리고/또는의 양쪽 피연산자는참이나거짓이어야 해요. 숫자(0포함), 문자열, 목록,비어있음같은 다른 값은 거짓으로 취급하지 않고TypeError를 발생시켜야 해요. (단락 평가로 평가되지 않는 우항은 검사하지 않아요.)
5.3 수학 연산 예외 (DivideByZeroError)
- 0으로 나누기 및 나머지: 나눗셈(
/) 및 나머지(%) 연산자를 평가할 때, 우항(분모)의 런타임 값이 0(0,0.0)인 경우 런타임은 호스트 시스템의 크래시나무한대(Infinity)/NaN반환을 허용하지 않고, 즉시MathError(또는DivideByZeroError) 예외를 발생시켜 콜스택을 풀어 주셔야 해요.
5.4 문자열 (String)
- 문자열 (String): 목록(List)과 동일한 인덱스 접근(
1번째) 및 길이 조회(길이) 문법을 지원해요.- 인덱스 접근: 1-based 인덱싱을 동일하게 적용하며, 범위를 벗어나면
IndexOutOfBoundsError를 발생시킨답니다. - 반환 타입: 인덱스 접근 결과는 길이 1인
[문자열]값이에요 (별도의 문자(Char) 타입이 없어요). - 불변성 (Immutability): 문자열은 불변(Immutable) 값이에요.
MemberExpression을 통한 인덱스 위치 재대입('이름'의 1번째를 "김"으로 정하자)은 허용되지 않으며, 시도할 경우ImmutableAssignmentError(또는TypeError)를 발생시켜 주셔야 해요. - 길이 조회:
'문자열'의 '길이'를 평가할 때 문자 개수를 정수로 반환해 주세요. - 유니코드 및 정규화 (NFC/Codepoint): 문자열의 인덱싱과 길이 측정은 항상 유니코드 코드포인트(Codepoint)를 기준으로 계산하며, 소스 코드 및 모든 입력 문자열은 NFC(완성형 정규화) 처리된 것으로 간주하여 호스트 언어 간의 파편화를 방지해야 한답니다.
- 문자열 순회:
ForEachLoop(마다 반복하자)를 통해 순회할 수 있으며, 매 반복마다 길이가 1인 문자열이 반환되어요.
- 인덱스 접근: 1-based 인덱싱을 동일하게 적용하며, 범위를 벗어나면
- 문자열 연결 (
+) 및 복합 대입: 이형(異形) 타입 간의+연산은 금지되지만, 문자열과 문자열 간의+연산은 허용되며 이어 붙인 새로운 문자열을 반환해요. 또한 복합 대입 연산인'문자열변수'에 "값"을 더하자(CompoundAssignment노드,operator: "+=")를 사용할 경우, 기존 문자열 인스턴스를 수정하는 것이 아니라 결합된 새 문자열을 생성하여 대상 변수 또는 속성에 재대입하는 방식으로 정상 동작해야 한답니다. - 문자열 비교 (
<,>,==): 문자열 간의 비교 연산자는 유니코드(Unicode) 사전순(Lexicographical) 기준을 따라요. - 내장 메서드: 모든 문자열 인스턴스는 다음과 같은 내장(Built-in) 메서드 호출을 기본 지원해야 해요:
<자르기>(시작, 끝): 부분 문자열을 반환해요. 숫자 범위 반복문과 동일하게 1-based 기준이며, 종료 인덱스의 문자까지 포함(Inclusive)하여 자른답니다.<바꾸기>(기존, 새것): 문자열 내에서 발견되는 모든(All) ‘기존’ 문자열을 ’새것’으로 전체 치환하여 새 문자열을 반환해요.<포함확인>(검색어): 포함 여부를 논리값으로 반환해 주세요.<분리하기>(구분자): 구분자를 기준으로 문자열을 쪼개어[목록]객체로 반환해요. 만약 구분자가 원본 문자열 내에 존재하지 않는다면, 원본 문자열 전체를 유일한 요소로 갖는 길이 1짜리 목록을 반환해야 한답니다.
5.5 전역 내장 함수 (Global Built-in Functions)
모든 런타임 환경은 별도의 임포트 없이 다음의 전역 유틸리티 함수들을 기본으로 제공해 주셔야 해요.
- 형변환 함수:
<숫자로>(값): 문자열이나 다른 타입의 값을 실수 또는 정수로 변환하여 반환해요. 변환이 불가능한 값일 경우ConversionError를 발생시켜 주세요.<문자로>(값): 숫자나 논리값 등 임의의 값을 문자열(문법적 직렬화)로 변환하여 반환한답니다.
- 아스키/유니코드 변환 함수:
<코드로>(문자열): 길이가 1인 문자열을 받아 해당하는 유니코드 코드포인트(정수)를 반환해요. (길이가 1이 아니면 에러를 발생시켜 주세요.)<글자로>(숫자): 코드포인트(정수)를 받아 해당하는 유니코드 문자(길이 1짜리 문자열)로 반환한답니다.
5.6 연산 순서 (Operator Precedence)
- 산술 연산자는 일반 수학 순서를 따라요.
*,/,%가+,-보다 먼저 묶이고, 같은 단계끼리는 왼쪽부터 묶어요(2 + 3 * 4는14,100 / 10 / 5는2). 괄호는 항상 가장 먼저예요. - 비교(
같다,크다등)와그리고/또는은 산술을 모두 계산한 뒤에 붙는 별개의 구조라서 이 표에 들어가지 않아요. 파서는 이 순서를 AST의BinaryExpression중첩으로 나타내고, 런타임은 트리를 그대로 평가하면 돼요.
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) 구문을 통해 개발자가 직접 제어하도록 강제한답니다.