From 82925e53aedc3c63e9f7dbf53b88eacfbd86c06e Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 18 Oct 2026 16:43:57 +0100 Subject: Fns that call themselves, blocks, several values, records' values and characters (Pure 027's stage 2c): a fn with a block (statements, return a, b; return alone gives nothing) or one a cycle of calls goes through runs as Pure's function (CALL), once for each kinds of arguments it is called with, in code of its own after the handlers' and views'; its parameters held by name as it begins, its let's lists, held values and registers each call's own (Pure's); -> types where it calls itself before a return says what it gives, a return's values wanted as those types; f(x) as a statement where it gives nothing, let a, b = f(x) and a, b = f(x) (lets and states) where it gives several; a view, a count, sum, max or min calls none; records' and variants' values: R { ... }, V.case(x), V.case and calls giving one onto the stack field by field, held by a name (name.field lets), passed and given back, matched (a case's value named, read only), appended (a call's value held first), an item and a record under an item's field passed as values (a function has its own lists), a list a value holds walked through a register, a list in a local (Pure's local type 4), R { ... } written where a let, an assignment, a return or an argument takes a value; never-run lines after a return or a break refused, no jump past an else whose then stops, the last arm of a match that names every case taken without asking; for ch in t: a text's characters as code points (a let's, a parameter's, a state's, a record's field, a message's text field), char(n); examples/json_tree.aup by recursive descent and a depth-first walk; 13 diagnostics, 2 accepted forms, a functions test against Python (40 random trees built and summed recursively, deepest and leaves as two values, 60 random texts' characters, a call without end failing as memory with nothing changed); the 2b compiler's modules for 49 sources byte for byte the same (json_tree.aup's match aside); three diagnostics' wording follows records being values --- docs/design/002-dsl-direction.md | 70 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md index 3b02291..49e6ecd 100644 --- a/docs/design/002-dsl-direction.md +++ b/docs/design/002-dsl-direction.md @@ -11,8 +11,8 @@ aup는 범용 언어가 아니라 **무엇이 무엇에 연결되는지를 선 ## 원칙 -1. 모든 구문은 Pure가 로드 전에 검증할 수 있는 것으로 컴파일된다. 무제한 반복·재귀는 없다. - 길이에 비례하는 연산은 이벤트 작업 예산을 쓰고, 넘으면 이벤트가 실패한다. +1. 모든 구문은 Pure가 로드 전에 검증할 수 있는 것으로 컴파일된다. 끝을 모르는 반복(`while`)과 재귀도 있다(Pure 027). + 끝남은 문법이 아니라 예산이 보증한다. 걸음은 이벤트 작업 예산을, 부름의 깊이는 메모리 몫을 쓰고, 넘으면 이벤트가 실패한다. 2. 이벤트가 트랜잭션이다. 효과는 이벤트 경계에서만 일어나고, 실패하면 아무것도 바뀌지 않는다. 3. 형태는 한 번만 선언한다. 같은 `record`가 저장·메시지·전송·보기에 쓰이고 표현은 런타임이 고른다. 4. 표면은 가독성을 우선한다. 줄바꿈으로 끝나는 문장, `{}` 블록, `state / input / on / view`. @@ -234,8 +234,8 @@ view summary = words.counted(tasks.count, "task", "tasks") 매개변수에 타입을 적지 않는다. 부르는 곳마다 인라인하므로 수·text·커서·인덱스 무엇이든 받는다. 본문의 타입 오류는 본문 위치에 `(in fn 'f' called at 줄:열)`을 붙여 알린다. - **런타임 호출이 없다.** 인자는 부를 때 한 번 계산해 스택에 두고, 본문은 Pure의 PICK으로 읽고 SLIDE로 치운다. - 인자를 여러 번 써도 다시 계산하지 않으며, 쓰지 않는 인자도 계산한다(값 전달). 재귀는 컴파일 오류다. - 그래서 실행량 상한과 로드 전 검증이 그대로 유지된다. + 인자를 여러 번 써도 다시 계산하지 않으며, 쓰지 않는 인자도 계산한다(값 전달). 재귀하는 fn과 블록 본문의 fn은 Pure의 함수로 + 돈다(아래 "스스로 부르는 fn", Pure 027의 2c). - **use는 라이브러리를 가져온다.** `use words`는 소스 옆의 `words.aup`, 없으면 `AUP_PATH`(`:`로 구분)의 디렉터리에서 찾는다. 라이브러리는 `fn`, `record`(아래), `use`만 선언하고 앱의 상태를 볼 수 없다(매개변수만 읽는다). 부를 때는 `words.counted(...)`처럼 라이브러리 이름을 붙인다. 순환 use는 오류이고, 라이브러리의 오류는 그 파일 경로로 알린다. @@ -752,3 +752,65 @@ on check { - 받아들이는 꼴 하나. - 중첩 값의 앱이 기대값을 낸다. 같은 이벤트를 두 번 해도 같은 몫이 늘고, 실패한 이벤트는 아무것도 남기지 않는다. - `examples/json_tree.aup`는 Pure의 검사가 JSONTestSuite와 무작위 문서 100개로 파이썬의 읽기와 대조한다. + +### 스스로 부르는 fn, 블록, 여러 값, 값으로서의 레코드, 글자 (Pure 027의 2c) + +`examples/json_tree.aup`가 이것으로 재귀 하강 파서와 깊이 우선 걷기로 다시 쓰였다. + +```text +fn value(d, i) -> Json, number { # 블록 본문의 fn. 스스로 부르면 결과의 타입을 적는다 + let items: list of Json # let의 리스트는 부를 때마다 제 것이다 + let j = skip(d, i) + if d[j] == 0x5b { + let v, k = value(d, j + 1) # 여러 값을 한 번에 받는다. v는 Json 값이다 + items.append(v) + return Json.arr(items), k + 1 # 레코드나 갈래의 값을 돌려준다 + } + ... +} +fn walk(x) { # 값을 주지 않는 fn: 부름은 문장이다 + match x { # 이름에 담긴 갈래의 값을 match한다 + arr(items) => { for y in items { walk(y) } } # 항목은 값으로 넘어간다 + _ => {} + } +} +on check { + let root, end = value(bytes(text(check.data)), 0) + walk(root) + for ch in check.name { n = n + number(ch) } # text의 글자(코드 포인트, i64) +} +``` + +- **fn이 Pure의 함수가 되는 때.** 블록 본문을 가졌거나, 부름의 고리가 자신을 지나면 Pure의 함수(CALL)로 돈다. 식 하나의 fn은 + 지금처럼 인라인한다. 인자의 타입마다 한 벌을 만든다(단형화). 레코드 값을 받는 인라인 fn도 함수로 돈다. +- **결과.** + - `return a, b`의 값들이 결과다. 값 없는 `return`과, 끝에 이르는 블록은 아무것도 주지 않는다. + - 결과의 타입은 처음 컴파일된 `return`이 정한다. 그보다 먼저 자신을 부르면 `-> 타입, …`을 적어야 한다(진단). + - 모든 `return`은 같은 타입을 준다. 결과가 있는 fn은 모든 길이 `return`으로 끝나야 한다. + - 적은 결과의 타입은 `return`의 값이 바란다. 그래서 `-> i64`인 fn의 `return 1`은 i64다. +- **부름.** 식 안에서 쓰거나, 값을 주지 않으면 문장으로 쓴다(`walk(root)`). 여러 값은 `let a, b = f(x)`나 `a, b = f(x)`(let과 수, + bool, i64 상태)로 받고, `return f(x)`로 그대로 넘긴다. +- **보기에서는 부르지 않는다.** 보기와 count·sum·max·min의 블록은 함수를 부르지 않는다(Pure가 그렇다). 핸들러와 함수가 부른다. +- **레코드의 값.** + - `R { … }`, `V.case(x)`, `V.case`, 레코드 값을 주는 부름, 이름에 담긴 레코드 값은 그 필드들로 스택에 놓인다. + - `let p = …`는 이름 하나에 레코드를 담는다. 필드는 `p.x`로 읽힌다(이름 `p.필드`의 let들). + - 항목(`for y in ys`의 y, `m.value` 같은 레코드 필드의 경로)은 함수에 값으로 넘어간다. 함수는 제 리스트를 가지므로 항목의 자리가 + 아니라 값을 받는다. + - `match`는 이름에 담긴 갈래의 값도 받는다. 경우의 이름은 읽기 전용이다. + - 리스트 값은 지역 칸에 담기고(Pure의 지역 칸 타입 4), `for y in items`가 레지스터에 묶어 걷는다. +- **도달하지 않는 코드.** `return`이나 `break` 뒤의 줄은 진단이 나온다. 모든 경우가 이름 붙은 `match`의 마지막 갈래는 묻지 않고 + 탄다. `if`의 then이 돌아가거나 멈추면 else를 건너는 점프를 두지 않는다. +- **글자.** + - `for ch in t`는 text의 글자를 코드 포인트(i64)로 돈다. t는 let, 매개변수, 상태, 레코드 값이나 메시지의 text 필드다. 돌기 전에 + 제 이름에 담는다. + - `char(n)`은 코드 포인트 하나의 text다. 서로게이트면 `encoding`, 0–0x10FFFF 밖이면 `limit`이다(Pure의 CHAR). + +검증: +- 진단 13건: 결과를 모른 채 자신을 부름, 보기의 부름, 값 없이 끝날 수 있는 fn, 서로 다른 결과, 도달하지 않는 줄, 값을 주는 부름의 + 문장, 여러 값을 이름 하나에, 이름과 값의 수, 모르는 타입, 커서 인자, 경우 이름에 대입, 갈래가 아닌 match, 접기 안의 부름. +- 받아들이는 꼴 둘. +- 함수의 앱이 파이썬과 같다. 무작위 트리 40개를 재귀로 짓고 합하고, 깊이와 잎 수를 여러 값으로 받는다. 무작위 text 60개의 글자 수, + 코드 포인트의 합, 넓은 글자 수, 그리고 `char`로 다시 지은 text다. 끝없는 재귀는 `memory`로 실패하고 아무것도 바꾸지 않는다. +- 모듈은 2b의 컴파일러와 바이트까지 같다(aup와 Pure의 aup 소스 49개). 다른 것은 `match`를 쓴 `json_tree.aup`뿐이다. +- 바뀐 진단 문구 셋: 레코드가 이제 값이라 `n.insert(at, T { … })`는 "expected text, not a record"다. let의 타입 목록에 리스트와 + 레코드가 들었다. 자신을 부르는 식 fn은 이제 보기에서만 거절된다. -- cgit v1.2.3