lowstruct 형식 명세 — 판 0.0.1
English · 한국어
정본은 영문 명세(lowstruct.md)이고, 이 문서는 그 번역입니다. 둘이 어긋나면 영문이 옳습니다.- 형식 이름: lowstruct. 확장자
.lows. 매체 형식(비공식):text/x-lowstruct. - 로우엔트(Lowent)와 어휘를 공유하지만 별개의 형식이다(부록 A).
.lows파일은 로우엔트 프로그램이 아니다. - 상태: 판 0.0.1 (초안). 호환을 약속하지 아니한다.
- 규범은 영문 명세다. 세 구현(
c/·js/·python/)과 적합성 사례(conformance/)는 명세를 따르고, 명세와 어긋나면 구현이나 사례를 고친다. - 문장의 끝이 «…한다» 이면 요구이고, «…아니한다» 이면 금지다.
(참고)로 시작하는 단락은 규범이 아니다.
1. 개요
lowstruct 는 사람이 손으로 고치는 설정 파일 형식이다. 로우엔트(Lowent) 언어의 어휘를 그대로 빌린다.
rem 서버 설정
title "my-app" .
server do
host "0.0.0.0" .
ports 80 443 .
tls do
cert "a.pem" .
key "a.key" .
end
end
windir "C:\\Windows" .- 문장은 이름들(경로) + 리터럴들(값) +
.이다.=·[ ]·,가 없다. do … end는 공통 앞부분을 묶는다.- 값이 여럿이면 목록이다.
- 역슬래시는 닫힌 이스케이프 집합으로만 읽힌다. 여러 줄 원문은
textheredoc 으로 적는다.
2. 파일
(1) 파일은 UTF-8 이다. 유효하지 않은 UTF-8 은 거부한다(E-LOWS-UTF8). 유효함은 RFC 3629 를 따른다 — 지나치게 긴 부호화, 서러게이트(U+D800–U+DFFF), U+10FFFF 초과는 유효하지 않다.
(2) 파일 맨 앞의 BOM(EF BB BF)은 거부한다(E-LOWS-BOM).
(3) 줄 끝은 LF 또는 CRLF 다. 처리기는 CRLF 를 LF 로 바꾼 뒤에 읽는다. 그 뒤에도 남은 CR 은 거부한다(E-LOWS-CR). 그러므로 text heredoc 의 값에 CRLF 는 남지 아니한다.
(4) 비 ASCII 글자는 주석, 문자열·문자 리터럴, heredoc 본문 안에서만 쓸 수 있다. 그 밖의 자리에 있는 글자가 아래 어휘 어디에도 속하지 않으면 거부한다(E-LOWS-CHARSET). NUL 과 [·=·#·;·, 도 여기에 든다.
(참고) 그래서 INI 나 TOML 파일을 잘못 넘기면 첫 문장에서 멈춘다.
3. 어휘
3.1 공백과 주석
(1) 공백은 스페이스(U+0020)·탭(U+0009)·줄바꿈(U+000A)이며, 토큰을 가르는 것 말고 뜻이 없다.
(2) 낱말 rem 부터 그 줄의 끝까지는 주석이다.
(3) note TAG 로 시작하는 여러 줄 주석은 같은 TAG 만 있는 줄에서 끝난다(3.5 의 heredoc 과 같은 규칙). 값을 만들지 아니한다.
3.2 이름과 예약어
(1) 이름은 [A-Za-z_][A-Za-z0-9_]* 다. 대소문자를 가린다. 점이 없다.
(2) 다음 일곱 낱말은 예약되어 있고 이름이 될 수 없다: rem note text do end true false. 이들은 어휘 단계에서 이미 주석·heredoc·블록·불리언이 되므로, «예약어를 이름으로 썼다» 는 진단은 따로 없다 — 그 자리에서 문법이 깨진다(예: server text 1 . 은 E-LOWS-TAG).
(3) 이름 바로 뒤에 따옴표가 붙으면 그 이름은 접두사다(3.4). u·U 밖의 접두사는 거부한다(E-LOWS-PREFIX). 띄어 쓰면 접두사가 아니다 — u "x" 는 이름 u 와 문자열 "x" 다.
3.3 수
(1) 수의 모양은 다음과 같다. 위의 갈래부터 차례로 맞춰 보고, 처음 맞는 것을 쓴다.
number = [ "+" | "-" ] , ( hexfloat | decfloat | hex | bin | dec ) ;
dec = digit , { [ "_" ] , digit } ;
hex = "0" , ( "x" | "X" ) , hexdigit , { [ "_" ] , hexdigit } ;
bin = "0" , ( "b" | "B" ) , bindigit , { [ "_" ] , bindigit } ;
decfloat = dec , "." , dec , [ dexp ] | dec , dexp ;
hexfloat = hex , [ "." , hexdigit , { [ "_" ] , hexdigit } ] , ( "p" | "P" ) , [ "+" | "-" ] , dec ;
dexp = ( "e" | "E" ) , [ "+" | "-" ] , dec ;(2) 부호는 붙여 써야 부호다. - 5 는 수가 아니다.
(3) _ 는 자릿수 사이에만 온다. 앞의 0 은 팔진이 아니다 — 0755 는 755 다. 팔진 표기는 없다.
(4) 점 앞뒤에 숫자가 있어야 부동소수다. 80. 은 정수 80 과 문장 끝이고, .5 는 수가 아니다.
(5) 수 바로 뒤에 [A-Za-z0-9_] 가 붙어 있으면 그 수는 잘못된 것이다(E-LOWS-NUMBER). 그래서 0x·0b(숫자 없는 진법 표시), 0o7, 1__0, 0x_1, 1_, 0x1p(지수 숫자 없음)가 거부된다.
(6) 정수의 값은 −2⁶³ 이상 2⁶⁴−1 이하여야 한다(E-LOWS-RANGE). -0 은 0 이다.
(7) 부동소수의 값은 IEEE 754 binary64 로, 가장 가까운 값으로 반올림하고 한가운데면 짝수 쪽으로 간다 (십진·십육진 모두). 결과가 유한하지 않으면 거부한다(E-LOWS-RANGE). inf·nan 을 적는 방법은 없다.
3.4 문자열과 문자
(1) 문자열은 "…", 문자는 '…' 다. 앞에 접두사 u·U 를 붙일 수 있다.
(2) 리터럴은 한 줄 안에서 닫혀야 한다(E-LOWS-UNCLOSED).
(3) 역슬래시로 시작하는 이스케이프는 다음 열넷뿐이다. 그 밖의 것은 거부한다(E-LOWS-ESCAPE).
| 적는 것 | 값 |
\\ \" \' | 0x5C · 0x22 · 0x27 |
\a \b \f \n \r \t \v \0 | 0x07 · 0x08 · 0x0C · 0x0A · 0x0D · 0x09 · 0x0B · 0x00 |
\xNN | 바이트 하나 (십육진 정확히 두 자리) |
\uXXXX · \UXXXXXXXX | 코드포인트 (정확히 네·여덟 자리). 서러게이트와 U+10FFFF 초과는 거부 |
(4) 값은 두 단계로 만든다.
- 리터럴을 먼저 바이트열로 만든다. 적힌 글자는 그 UTF-8 바이트가,
\xNN은 그 바이트가,\u·\U는 그 코드포인트의 UTF-8 바이트가 된다. - 접두사가 원소를 정한다. 접두사가 없으면 바이트가 원소다.
u는 바이트열을 UTF-8 로 풀어 UTF-16 코드 유닛으로,U는 코드포인트로 센다. 풀 수 없으면(예:u"\xE9") 거부한다(E-LOWS-ESCAPE).
(5) 문자 리터럴은 (4) 로 만든 원소가 정확히 하나여야 한다. 없으면 E-LOWS-CHAR-EMPTY, 둘 이상이면 E-LOWS-CHAR-WIDTH 다. 'é' 는 바이트 둘이라 거부되고 u'é' 는 된다.
(6) 접두사 없는 문자열은 UTF-8 이라는 보장이 없는 바이트열이다("\xE9", "a\0b"). 텍스트가 필요한 읽는 쪽은 UTF-8 검사를 한다. C 로 읽을 때는 NUL 로 끝을 찾지 않는다.
3.5 heredoc
(1) text [u|U] TAG 로 시작해, 앞뒤 공백·탭을 빼면 TAG 만 남는 줄에서 끝난다. TAG 는 이름이다. 여는 줄에 다른 것이 있으면 E-LOWS-TAG, 접두사 자리에 u·U 밖의 이름이 있으면 E-LOWS-PREFIX, 닫는 줄이 없으면 E-LOWS-UNCLOSED 다.
(2) 본문은 여는 줄 다음 줄부터 닫는 줄 앞 줄까지이며, 줄 사이를 LF 로 잇는다. 마지막 줄 뒤의 LF 는 넣지 아니한다. 이스케이프를 풀지 아니한다. 값은 3.4 (4) 의 2 단계를 거친다.
3.6 윈도우 경로
(1) 한 줄짜리 값의 역슬래시는 \\ 로 적는다: windir "C:\\Windows" .
(2) (참고) "C:\temp\new" 는 거부되지 않는다. \t·\n 이 집합 안에 있어 탭과 줄바꿈이 된다. (1) 을 지키면 생기지 않는다.
4. 문장과 데이터 모델
4.1 문법
file = { stmt } ;
stmt = path , ( "do" , stmt , { stmt } , "end" | { value } , "." ) ;
path = NAME , { NAME } ;
value = NUMBER | "true" | "false" | STRING | CHAR | TEXT ;(1) 경로는 이름들이고 값은 리터럴들이다. 첫 리터럴에서 경로가 끝난다. 값 뒤에 이름이 오면 거부한다(E-LOWS-ORDER).
(2) 문장은 이름으로 시작한다(E-LOWS-PATH). do 없는 end 는 E-LOWS-END, 닫히지 않은 문장·블록은 E-LOWS-UNCLOSED 다.
(3) a b c 1 . 과 a do b do c 1 . end end 는 같은 나무를 만든다.
4.2 나무
(1) 문서는 나무 하나다. 가지는 이름 → 자식이고, 자식의 순서는 소스 순서다. 잎은 값의 목록이다.
(2) 한 잎의 값은 모두 한 종류다(E-LOWS-MIX). 종류는 아홉이다.
| 종류 | 리터럴 | 원소 |
int | 42 -5 0xFF | −2⁶³ … 2⁶⁴−1 |
float | 1.5 0x1p3 | binary64, 유한 |
bool | true false | |
str | "…", text TAG | 바이트 |
u_str | u"…", text u TAG | UTF-16 코드 유닛 |
U_str | U"…", text U TAG | 코드포인트 |
char · u_char · U_char | 'a' · u'é' · U'😀' | 원소 하나 |
int 와 float 도 다른 종류다 — 1 2.0 은 섞인 것이다.
(3) 값이 하나인 잎이 스칼라다. 값이 없는 잎(flag .)은 빈 목록이며, 그 키가 있다는 표시다. 구현은 이 잎의 종류를 empty 로 보인다.
(4) null 과 날짜 타입은 없다. 목록 안의 목록, 목록 안의 표도 없다 — 같은 종류 여럿은 이름 붙인 블록으로 적는다.
remote origin do
url "https://example.invalid/r.git" .
end
remote backup do
url "https://example.invalid/b.git" .
end4.3 한 경로에는 한 작성자
| 규칙 | 진단 |
| 같은 잎을 두 번 쓸 수 없다 | E-LOWS-DUP |
잎이면서 가지일 수 없다 (a 1 . 과 a b 2 .) | E-LOWS-SHAPE |
| 블록으로 연 경로는 그 블록만 쓴다. 다시 열 수도, 밖에서 평평한 문장으로 더할 수도 없다 | E-LOWS-SEALED |
| 이미 있는 경로를 블록으로 열 수 없다 | E-LOWS-SEALED |
| 빈 블록 | E-LOWS-EMPTY |
블록은 64 겹까지 겹친다. 65 번째 do 는 거부한다 | E-LOWS-DEPTH |
평평한 문장끼리는 앞부분을 나눠 써도 된다(package name … / package version …).
5. 진단
(1) 거부는 진단 하나로 알린다: 줄(1 부터), 열(1 부터, 코드포인트 단위), 코드, 문장. 코드는 안정된 이름이다. 문장은 판마다 바뀔 수 있다.
(2) 처리기는 다음 순서로 읽고, 처음 만난 잘못 하나를 알린다.
- 파일 전체의 UTF-8 → BOM → CR (2 절).
- 파일 전체의 어휘(3 절) — 그러므로 어휘 잘못은 그보다 앞에 있는 구조 잘못보다 먼저 알린다.
- 문장과 나무(4 절), 소스 순서대로.
(3) 위치는 다음 자리다.
| 코드 | 자리 |
E-LOWS-UTF8 · E-LOWS-CR · E-LOWS-CHARSET | 그 바이트·글자 |
E-LOWS-BOM | 1:1 |
E-LOWS-ESCAPE (이스케이프 모양·코드포인트) | 그 역슬래시 |
E-LOWS-ESCAPE (UTF-8 로 못 풂) · E-LOWS-UNCLOSED(리터럴) · E-LOWS-CHAR-* · E-LOWS-PREFIX(리터럴) · E-LOWS-NUMBER · E-LOWS-RANGE | 그 리터럴·수의 첫 글자(접두사·부호 포함) |
E-LOWS-TAG · E-LOWS-PREFIX(heredoc) · E-LOWS-UNCLOSED(heredoc) · heredoc 값의 E-LOWS-ESCAPE | note·text 낱말 |
E-LOWS-ORDER | 값 뒤에 온 이름 |
E-LOWS-UNCLOSED(문장) | 마침표가 와야 할 자리의 토큰. 파일이 끝났으면 문장의 첫 이름 |
E-LOWS-UNCLOSED(블록) | 파일의 마지막 토큰 |
E-LOWS-EMPTY · E-LOWS-DEPTH | 그 do |
E-LOWS-PATH · E-LOWS-END | 그 토큰 |
E-LOWS-MIX · E-LOWS-DUP · E-LOWS-SHAPE · E-LOWS-SEALED | 문장의 첫 이름 |
(4) 코드 전체: UTF8 BOM CR CHARSET UNCLOSED ESCAPE PREFIX TAG CHAR-EMPTY CHAR-WIDTH NUMBER RANGE PATH END ORDER MIX DUP SHAPE SEALED EMPTY DEPTH (각각 앞에 E-LOWS-).
6. 구현이 값을 보이는 모양 (참고)
| 종류 | C (lowstruct.h) | Node.js | Python |
int | {magnitude, negative} · lows_get_i64/u64 | BigInt | int |
float | double | Number | float |
bool | bool | Boolean | bool |
str | 바이트 포인터 + 길이 | Uint8Array | bytes |
u_str · U_str | u16·u32 포인터 + 길이 | 수의 배열 | 수의 리스트 |
char 셋 | u32 | Number | int |
부록 A. 로우엔트와의 관계
- lowstruct 는 로우엔트와 문법을 공유하는 별개의 형식이다. 저장소·명세·판·진단·일정이 따로 있고, 이 문서만이 lowstruct 의 규범이다. 로우엔트 문서는 출처일 뿐 규범이 아니다.
- 리터럴(3.3–3.5)은 2026-09-26 의 로우엔트 명세(언어 개정 1.3) §6.1.4·부록 A.6 과 같게 고정했다. 로우엔트가 뒤에 리터럴을 바꿔도 lowstruct 는 따라 바뀌지 아니한다 — 들이려면 lowstruct 의 새 판으로 정한다. 판 0.0.1 에서 같은 리터럴이 두 곳에서 다른 값을 내면 결함이다. (참고) 판 0.0.1 을 만들며 한 차분 검사에서 lowentc 가
0x를 0 으로 받던 결함이 드러났고 로우엔트 쪽에서 고쳤다. - 로우엔트 패키지 매니페스트
pkg.low는 고치지 않고 lowstruct 로 읽힌다(사례08-lowent-pkg). make point do x 1 . y 2 . end의 몸통은 lowstruct 블록과 같은 모양이다. 다음 판의 스키마(부록 D)의 출발점이다.- lowstruct 파일은 로우엔트 프로그램이 아니다.
부록 B. 적합성
(1) conformance/accept/*.lows 는 받아들여야 하며, 부록 C 의 덤프가 같은 이름의 .dump 와 바이트 단위로 같아야 한다.
(2) conformance/reject/*.lows 는 거부해야 하며, 코드가 파일 첫 줄 rem expect E-LOWS-… 와 같아야 한다.
(3) 적합한 구현은 (1)·(2) 를 모두 통과한다. 위치(5 (3))는 세 구현이 차분 퍼즈(tools/difffuzz.py)로 서로 맞춘다.
부록 C. 정규 덤프
구현을 서로 견주려고 쓰는 줄 단위 표현이다. 부동소수를 글자로 옮기는 방법이 언어마다 달라, 비트를 적는다.
lowstruct-dump 1
<경로>\t<종류>\t<값> <값> …- 잎마다 한 줄, 나무를 소스 순서로 깊이 먼저 돈다. 경로는 이름을
.으로 잇는다. - 값:
int는 십진(음수는-),float는 binary64 비트를 큰 끝 순서 십육진 16 자리(소문자),bool은true/false,str은x:뒤에 바이트 십육진(소문자),u_str·U_str는[65,66]처럼 십진을 쉼표로 이은 것, 문자 셋은 십진. 값이 없으면 종류는empty이고 값 칸은 비어 있다. - 파일은 LF 로 끝난다.
부록 D. 다음 판으로 미룬 것
- 로우엔트
struct를 스키마로 쓰기.make … do … end몸통 = lowstruct 블록. 모든 칸 채움 = 필수 키 검사, 선언에 없는 칸 = 오타 검사, 칸 타입 = 값 범위·종류 검사. 목록 ↔slice, 이름 붙인 블록 ↔ 맵, 빈 잎 ↔bool/option의 대응을 정해야 한다. - 이름 붙은 값(심벌).
level debug .는 지금level가지 아래debug잎이다.be로 경계를 밝히는 안이 있다. - 블록 봉인(4.3)을 풀지. 판 0.0.1 은 엄격한 쪽이다.
- 정규형 포매터. 한 줄 값의 역슬래시는 언제나
\\로 낸다. - 경로 모양 문자열 경고. 드라이브 문자나
\\server로 시작하는 문자열 안의\t·\n등을 경고하는 안.