5 환경

5.1 상태와 메모리

(1) 처리기는 호스트가 준 바이트 덩이 하나 안에서만 돈다. 상태를 연 뒤로 처리기는 운영체제에 메모리를 청하지 않는다. 덩이가 모자라면 그 일이 PINAX_ERR_NO_ROOM 으로 실패할 뿐 덩이 밖에 쓰지 않는다.

(2) 한 상태 안의 모든 값은 다음 가운데 한 곳에 있다: 선언된 전역(8절), 스택과 지역 변수의 칸(9절), 작업의 구역(12.2), 그리고 호스트가 주고 보증하는 메모리(묶은 저장소와 공유 저장소 8.12, 8.13, 넘겨받은 문자열, KEEP_STR 결과 19.3). 그 밖의 자리는 없다. 값이 다른 값을 가리킬 때는 번호로 가리킨다(줄의 번호, 핸들).

(3) 상태는 전역 변수나 스레드 지역 변수를 쓰지 않는다. 그래서 두 상태는 서로 다른 스레드에서 동시에 돌 수 있다. 한 상태는 한 번에 한 스레드만 쓴다.

5.2 적재

(4) 상태는 소스를 몇 개든 차례로 받는다. 소스 하나를 적재할 때 처리기는 다음을 차례로 한다.

  1. 소스를 토큰으로 나누고(6절) 구문을 읽는다.
  2. 위에서 아래로 한 번 읽으며 이름, 타입, 스택의 깊이, 실패의 처리, owned 값의 넘겨줌을 검사한다.
  3. 모두 지켰으면 정의 밖에 적은 코드를 적힌 차례로 한 번 돌린다.

(5) 정의 밖의 코드(적재 코드(load code))는 그 소스의 낱말과 전역을 쓸 수 있다. 낱말 정의 안의 코드는 그 낱말이 불릴 때 돈다.

05-load-order.pnx

use io
"first: code outside definitions runs once, at load" io.println
word greet (gives s str) "second: a word runs when it is called" end
greet io.println

출력

$ pnx 05-load-order.pnx
first: code outside definitions runs once, at load
second: a word runs when it is called

(6) (4)의 1, 2 에서 거절하면 3 은 일어나지 않는다. 소스 앞부분에 적은 적재 코드도 돌지 않는다.

05-refused-runs-nothing.pnx

use io
"this line is never printed" io.println
word broken (n int gives r int) n "one" add end

거절

$ pnx 05-refused-runs-nothing.pnx
05-refused-runs-nothing.pnx:3:41: this word takes two int values, or two of the same declared type

(7) 적재 코드가 결함으로 멈추면 그 적재는 실패하고, 그 소스가 더한 낱말과 전역은 모두 없어진다. 앞서 적재한 소스의 전역에 그 코드가 넣은 값과 호스트 낱말이 이미 한 일은 되돌리지 않는다.

(8) 적재에는 두 방식이 있다. 코드 적재(code load)는 이 문서 전체를 받는다. 자료 적재(data load)는 선언과 값만 받고 낱말 정의, 낱말 부르기, 제어를 거절한다(13.4). 설정 파일처럼 믿지 못하는 글을 읽을 때 쓴다.

5.3 실행

(9) 호스트는 이름과 서명으로 낱말을 찾고(19절) 인자를 주어 돌린다. 낱말이 끝나면 결과를 받는다. 실행은 명령 수의 예산 안에서 돈다. 예산이 다하면 실행은 멈춘 자리를 기억하고 호스트에 돌아간다. 호스트가 이어 돌리면 멈춘 자리부터 계속한다. 예산은 결과를 바꾸지 않는다.

(10) 아래는 호스트 프로그램 전체다. 낱말 하나를 등록하고, 소스를 적재하고, 스크립트의 낱말을 부른다.

05-embed.c

#include "pinax.h"
#include <stdio.h>

/* A host word: the script calls it as calc.tenfold. */
static pinax_err tenfold(pinax_call *call, void *user, const pinax_args *args) {
    (void)call; (void)user;
    if (args->i[0] > INT64_MAX / 10 || args->i[0] < INT64_MIN / 10)
        return PINAX_ERR_OVERFLOW;              /* as the script's own mul would: a fault */
    args->ri[0] = args->i[0] * 10;
    return PINAX_OK;
}

int main(void) {
    static unsigned char block[1 << 20];          /* the whole state lives in here */
    pinax_config config = {.struct_size = sizeof config, .budget = 100000};
    pinax_state *st;
    if (pinax_open((pinax_mem){block, sizeof block}, &config, &st) != PINAX_OK)
        return 1;

    static const pinax_word_def words[] = {{"tenfold", "(n int gives r int)", tenfold, NULL, 0}};
    pinax_register(st, "calc", words, 1);

    static const char source[] =
        "use calc\n"
        "word answer (n int gives r int) n calc.tenfold 2 add end\n";
    pinax_str text = {(const unsigned char *)source, sizeof source - 1};
    if (pinax_load(st, "main", text, PINAX_LOAD_CODE) != PINAX_OK) {
        pinax_str why = pinax_error_text(st);
        fprintf(stderr, "%.*s\n", (int)why.len, (const char *)why.ptr);
        return 1;
    }

    pinax_word answer;
    pinax_find(st, "answer", "(n int gives r int)", 0, &answer);
    int64_t in = 4, out = 0;
    pinax_args args = {.i = &in, .ri = &out};
    pinax_err err = pinax_run(st, answer, &args);
    while (err == PINAX_SUSPENDED)                 /* the instruction budget ran out */
        err = pinax_resume(st, &args);
    printf("answer(4) = %lld\n", (long long)out);
    pinax_close(st);
    return 0;
}

출력

$ ./05-embed
answer(4) = 42

(11) 명령 pnx 는 이 라이브러리를 쓰는 호스트 하나다. 파일을 적재해 적재 코드를 돌리고, 표준 호스트 모듈(17절)을 등록한다. 이 문서의 예제는 대부분 pnx 로 돌린 것이다(20절).