CuffScript 가이드
CuffScript 문법 전체와, 이 브라우저 IDE에서 코드를 실행할 때 알아두면 좋은 내용을 정리했습니다. 왼쪽 목차를 눌러 원하는 항목으로 바로 이동할 수 있습니다.
CuffScript란
CuffScript는 자연어에 가까운 키워드(set, to,
do:, end)로 변수·함수·제어 흐름을 표현하는 인터프리터
언어입니다. Python처럼 들여쓰기로 블록을 구분하고, 배열과 문자열은 1부터 시작하는
인덱스를 사용하며, 정규식은 \d, \w 같은 기호 대신
[num], [str] 같은 읽기 쉬운 토큰으로 표현합니다.
이 IDE 사용법
- 실행 (Ctrl/Cmd + Enter) — 현재 열려 있는 탭을 진입점으로 실행합니다.
-
AST 보기 — 코드를 실행하지 않고 토큰·구문 트리만 확인합니다.
cuffc --ast와 동일한 정보입니다. -
표준 입력(stdin) —
input()은 실제 키보드 입력을 기다리지 않고, stdin 탭에 미리 적어 둔 텍스트를 위에서부터 한 줄씩 읽습니다. 다 읽고 나면 빈 문자열을 반환합니다. -
파일 탭 —
+버튼으로 파일을 추가해use ... from ...로 서로 불러오는 멀티파일 프로젝트를 만들 수 있습니다. 실행은 항상 현재 활성 탭을 진입점으로 삼습니다. - 공유 링크 — 현재 프로젝트 전체를 URL에 담아 복사합니다. 별도 서버 저장 없이 링크만으로 코드를 주고받을 수 있습니다.
- 다운로드 — 현재 탭의 내용을
.cuff파일로 저장합니다. - 작업 내용은 브라우저 localStorage에 자동 저장되어, 새로고침해도 유지됩니다.
변수·상수 선언
값을 처음 만들 때는 항상 set [자료형] [이름] to [값] 형태를 쓰고, 이후
값을 바꿀 때는 change [이름] to [값]을 씁니다.
set number age to 25
set str name to "Alice"
set empty data to empty
change age to 26
change name to "Bob"
상수는 set constant [자료형] [이름] to [값]으로 선언합니다. 이름은
반드시 전체 대문자(UPPER_CASE)여야 하며, 이후 change로
값을 바꾸려 하면 런타임 에러가 발생합니다.
set constant number MAX_RETRIES to 3
set constant str API_URL to "https://cufflang.dev"
자료형
| 타입 | 설명 | 예시 |
|---|---|---|
number |
정수·실수 | 5, 3.14 |
str |
문자열 | "hello" |
boolean |
참/거짓 | true, false |
list |
1-Based 순서 목록 | ["a", "b"] |
map |
문자열 키 사전 | {"k": "v"} |
empty |
값 없음 | empty |
함수 매개변수는 Python처럼 타입 표기 없이 자유롭게 받습니다. f-스트링(f"...")으로
문자열 안에 표현식을 끼워 넣을 수 있고, 중괄호 자체를 출력하려면
{{ }}처럼 두 번 씁니다.
set number x to 5
print(f"x + 1 = {x + 1}")
print(f"JSON 느낌: {{\"key\": \"value\"}}")
콜론 규칙과 주석
가독성을 언어 차원에서 강제하기 위해, 문자열을 제외한 모든 콜론(:)은
앞 공백 절대 금지, 뒤 공백 권장 규칙을 따릅니다. 어기면 렉서가 즉시
문법 에러를 냅니다. do: 뒤 실행부가 한 줄에서 끝나면 들여쓰기 없는
한 줄 축약형으로 쓸 수 있습니다.
한 줄 주석은 note: 내용, 여러 줄 주석은 note: 다음 줄부터
endnote 직전까지입니다.
note: 한 줄 주석
if x is 10 do: print("통과") note: 같은 줄에 덧붙인 주석도 가능
note:
여러 줄 주석 영역입니다.
들여쓰기와 줄바꿈은 자유롭게 구성할 수 있습니다.
endnote
비교·부정 연산자
is는 일반 동등 비교, IS는 영문 대소문자를
무시하는 비교입니다. !는 불리언 값을 반전시킵니다.
set str input_text to "Apple"
if input_text is "apple" do: print("대소문자가 달라 실행되지 않음") end
if input_text IS "apple" do: print("대소문자 무시라서 실행됨") end
set boolean is_active to false
if !is_active do: print("반전되어 실행됨") end
인덱싱과 슬라이싱
리스트·문자열의 첫 번째 위치는 1번입니다.
0번 인덱스는 존재하지 않으며 접근 시 런타임 에러가 발생합니다.
음수 인덱스는 뒤에서부터 세며 맨 뒤는 -1입니다. 슬라이싱은
[시작~끝]처럼 물결(~)로 표현하며 양쪽 끝을 모두
포함합니다.
set list colors to ["red", "green", "blue", "yellow"]
print(colors[1]) note: "red"
print(colors[-1]) note: "yellow"
print(colors[2~3]) note: ["green", "blue"]
조건문과 반복문
조건 분기는 if ... do: ... else if ... do: ... else do: ... end
형태입니다. 반복문은 세 가지입니다: 범위를 도는 loop repeat, 조건이
참인 동안 도는 loop while, 조건-반복이 결합된 loop match.
stop은 가장 가까운 반복문 하나만 즉시 종료합니다. 블록형 구문은
들여쓰기가 필수이며, 여는 만큼 end도 정확히 있어야 합니다.
if score >= 90 do: print("우수")
else if score >= 80 do: print("장려")
else do: print("노력")
end
loop repeat i to 1 ~ 10 do:
if i is 4 do:
stop
end
print(f"회전 라운드: {i}")
end
리스트·맵 조작
메서드 대신 자연어 구문으로 컬렉션을 다룹니다: 리스트 끝에 추가는
add ... to ..., 인덱스/키 값 변경은 change ... to ...,
제거는 remove ... from .... 맵에 없는 키에 값을 대입하면 새 키가
생깁니다.
set list inventory to ["sword", "shield"]
add "potion" to inventory
change inventory[1] to "magic_staff"
remove 2 from inventory
set map profile to {"name": "Bob"}
change profile["level"] to 50
remove "level" from profile
함수 정의
함수도 set으로 시작하며, 한 줄 축약형은 허용되지 않고 항상 들여쓰기된
블록으로 작성해야 합니다. 값을 반환하려면 returnable을 붙이고
return을 씁니다. 호출은 괄호 ()만 사용하며
do:는 붙이지 않습니다.
set returnable function fib(n) do:
if n <= 1 do:
return n
end
return fib(n - 1) + fib(n - 2)
end
print(f"fib(10) = {fib(10)}")
중첩 함수 정의와 클로저는 지원하지 않습니다. 함수는 전역 스코프와 자기 자신의 로컬 스코프만 볼 수 있습니다.
비동기 (async / await)
async 함수를 await 없이 호출하면 즉시 실행되지 않고
큐에 쌓이며, 최상위 스크립트의 동기 코드가 모두 끝난 뒤 쌓인 순서(FIFO)대로
실행됩니다. await를 붙이면 지금 바로 실행되고 결과값(있다면)을
돌려받습니다.
set async function notify() do:
print("[비동기] 처리 완료")
end
set async returnable function fetch_score() do:
return 87
end
print("[동기] 시작")
notify() note: 큐에 쌓임 — 지금 실행되지 않음
set number score to await fetch_score() note: await는 즉시 실행
print(f"[동기] 점수 = {score}")
print("[동기] 끝")
note: 이후에 큐에 있던 notify()가 실행됩니다.
스코프와 global
함수 안에서 선언한 변수는 그 함수 안에서만 유효합니다. 함수 안에서 전역 변수를
수정하려면 먼저 change [이름] to global로 선언한 뒤, 다음 줄에서 실제
값을 바꿉니다.
set number counter to 0
set function increment() do:
change counter to global
change counter to counter + 1
end
increment()
increment()
print(f"counter = {counter}")
or_else
try-catch 대신, 실패할 수 있는 구문 뒤에 or_else do: ... end를 붙여
에러를 처리합니다. 블록 안에서는 change로 기존 변수를 채우거나 새
변수를 선언할 수 있습니다.
set number a to 10
set number b to 0
set number result to a / b or_else do:
print("0으로 나누기 실패, 기본값으로 대체")
change result to -1
end
print(f"result = {result}")
에러 코드 체계
모든 에러는 고유한 숫자 코드(예: E4006)와 하나의 범주를 가집니다.
코드의 앞자리만 봐도 어느 단계에서 발생했는지 알 수 있습니다.
| 범위 | 범주 | or_else로 복구 가능? |
|---|---|---|
1000~1999 |
Lexical (토크나이저) | 불가능 |
2000~2999 |
Syntax (파서) | 불가능 |
3000~3099 |
Regex Syntax (패턴 컴파일) | 불가능 |
3100~3999 |
Regex Runtime (스텝/시간 제한 등) | 가능 |
4000~4999 |
Runtime (인터프리터) | 가능 |
5000~5999 |
Module (use/from) | 가능 |
9000~9999 |
Internal (엔진 내부 버그) | 불가능 |
프로그램 자체가 잘못된 경우(문법 오류 등)는 이미 실행 중인 코드 안에서 나타날 수
없으므로 or_else가 잡지 않으며, "정상적인 코드가 나쁜 상황(0으로
나누기, 없는 파일 등)을 만난 경우"만 or_else로 복구할 수 있습니다.
패턴 매칭 — 기본 사용법과 토큰
CuffScript는 \d, \w, ^, $ 같은
전통적인 정규식 기호 대신 [num]처럼 읽을 수 있는 대괄호 토큰을
씁니다. is / IS로 검사하면 기본적으로
문자열 전체 일치를 검증합니다.
| 토큰 | 의미 |
|---|---|
[num] |
숫자 1개 |
[let] |
영문 알파벳 1개 |
[low] / [up] |
영문 소문자 / 대문자 1개 |
[str] |
영문자 또는 숫자 1개 |
[word] |
영문자·숫자·언더바 1개(식별자용) |
[sp] |
공백 문자 1개 |
[nl] |
줄바꿈 문자 |
[any] |
임의의 문자 1개 |
[int] / [float] / [hex] |
부호 있는 정수 / 실수 / 16진수 문자 |
[email] / [phone] / [url] |
이메일 / 대한민국 전화번호 / URL 프리셋 |
[edge] |
단어 경계 |
[start] / [end] |
문자열 시작 / 끝 앵커 |
[one:a|b] |
후보 중 하나 선택 |
[abc] / [!abc] |
문자 세트 / 부정 문자 세트 |
N / + / * / ? / N~M
|
정확히 N개 / 1개 이상 / 0개 이상 / 0~1개 / N~M개 |
(...) |
캡처 그룹 (1-Based 접근) |
<name:...> |
이름 지정 캡처 |
[ ] ( ) +
* ? ~ | .
\ : 같은 특수기호 자체를 글자로 검사하려면
\로 이스케이프합니다. 정규식 안의 콜론도 언어 전체 규칙과 동일하게
앞 공백이 금지됩니다 ([one:a|b]는 되지만 [one :a|b]는
문법 에러).
if phone is "010-[num]4-[num]4" do: print("올바른 번호") end
if filename is "[str]+\.[one:jpg|png|gif]" do: print("이미지 파일") end
([any]+)+처럼 초보자가 실수하기 쉬운 수량자 중첩으로 인한 파국적
백트래킹을 막기 위해, 엔진은 최대 매칭 스텝 수와 시간 제한을 두고 있습니다. 한도를
넘으면 Regex Runtime Error로 안전하게 중단됩니다.
match / find / replace / split / count
match는 캡처 그룹 값을 꺼낼 때 씁니다. 실패하면 empty가 됩니다.
set str serial to "SN-2026-998"
set match result to match serial from "SN-([num]4)-([num]+)"
if result is not empty do:
print(f"연도: {result[1]}") note: "2026"
end
note: 이름 지정 캡처는 맵처럼 키로 조회
set match res to match "2026-12-25" from "--"
print(res["year"])
find는 본문 속 부분 검색입니다. 단독으로는 첫 매칭 문자열 하나를,
g 플래그를 붙이면 모든 매칭을 1-Based 리스트로 반환합니다. 매칭이
없으면 empty입니다.
set list tickets to find "T-[num]3" from article g
print(tickets[1])
replace는 패턴에 맞는 부분을 다른 문자열로 바꿉니다. g를 붙이면 전체 치환입니다.
set str masked to replace "[num]4-[num]4" in phone_log to "****-****"
split은 패턴을 기준으로 문자열을 나눕니다.
set list parts to split "apple, banana,cherry" by ",[sp]*"
count는 패턴이 등장한 횟수를 셉니다. 없으면 0입니다.
print(count "[num]+" in article)
find, match, count, replace 뒤에는
플래그를 붙일 수 있습니다: i(대소문자 무시), g(전체
탐색), m(멀티라인). 여러 개를 붙일 땐 gi처럼 이어
씁니다.
퀵 레퍼런스
[문자 토큰]
[num] [let] [low] [up] [str] [word] [sp] [nl] [any]
[프리셋 토큰]
[int] [float] [hex] [email] [phone] [url] [edge] [start] [end]
[수량자]
N + * ? N~M N~ ~M (뒤에 ? 붙이면 Lazy)
[선택·세트]
[one:a|b] [abc] [!abc]
[그룹]
(...) 캡처 그룹, 1-Based 인덱스
<name:...> 이름 지정 캡처
[명령어]
is / IS match find (g) replace ... to ... (g) split ... by ... count ... in ...
플래그: i(무시) g(전체) m(멀티라인)
use ... from ...
같은 프로젝트 안의 다른 .cuff 파일을 불러올 때는
use [파일명] from [상대경로]를 씁니다. 이 IDE에서는 파일 탭을 여러 개
만들어 이 문법을 그대로 시험해 볼 수 있습니다. 모듈 관련 구문은 반드시 한 줄로만
작성해야 합니다.
note: lib/greetings.cuff
set constant str DEFAULT_GREETING to "Hello"
set returnable function greet(name) do:
return DEFAULT_GREETING + ", " + name + "!"
end
note: main.cuff
use greetings from ./lib
print(greet("CuffScript"))
print(DEFAULT_GREETING)
내장 DLC 목록
공식 내장 라이브러리는 use DLC:이름 한 줄로 불러옵니다.
| DLC | 대표 함수 |
|---|---|
DLC:math |
sqrt, abs, pow, round,
floor,
ceil, min, max
|
DLC:string |
upper, lower, trim, length,
contains, starts_with, ends_with
|
DLC:time |
now, timestamp |
DLC:random |
random, random_int |
DLC:list |
sort, reverse, join, unique (원본은
바꾸지 않고 새 리스트를
반환) |
DLC:convert |
to_number, to_str, to_boolean |
DLC:network |
fetch, get, post |
DLC:network의
함수를 호출하면 항상 "호스트가 제공해야 한다"는 런타임 에러를 냅니다.
use DLC:network 선언 자체는 문제없이 되므로, or_else로
감싸 실패를 처리하는 코드를 연습해 볼 수 있습니다.
알려진 제한사항
-
표준 입력 — 실제 키보드 입력이 아니라, stdin 패널에 미리 적어 둔
텍스트를 한 줄씩 소비합니다. 다 소비하면
input()은 빈 문자열을 돌려줍니다. - 네트워크 —
DLC:network는 항상 사용 불가 에러를 냅니다 (위 참고). -
재귀 깊이 — 함수 호출 깊이가 1,000을 넘으면
StackOverflow(E4017)런타임 에러로 안전하게 중단됩니다. - 실행 시간 제한 — 이 IDE는 무한 루프로부터 탭을 보호하기 위해 실행 시간에 상한을 둡니다 (기본 8초, 언제든 직접 중단 가능).
- 중첩 함수 정의와 클로저는 언어 자체에서 지원하지 않습니다.