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에 자동 저장되어, 새로고침해도 유지됩니다.
무한 루프 주의 CuffScript 코드는 브라우저 안의 별도 워커에서 실행됩니다. 실행이 너무 오래 걸리면 (기본 8초) 자동으로 중단되며, 언제든 중단 버튼으로 직접 멈출 수도 있습니다.

변수·상수 선언

값을 처음 만들 때는 항상 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
ReDoS 방어 ([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는 이 IDE에서 항상 실패합니다 네트워크 접근은 엔진 자체에 샌드박스 구현이 없어, DLC:network의 함수를 호출하면 항상 "호스트가 제공해야 한다"는 런타임 에러를 냅니다. use DLC:network 선언 자체는 문제없이 되므로, or_else로 감싸 실패를 처리하는 코드를 연습해 볼 수 있습니다.

알려진 제한사항

  • 표준 입력 — 실제 키보드 입력이 아니라, stdin 패널에 미리 적어 둔 텍스트를 한 줄씩 소비합니다. 다 소비하면 input()은 빈 문자열을 돌려줍니다.
  • 네트워크DLC:network는 항상 사용 불가 에러를 냅니다 (위 참고).
  • 재귀 깊이 — 함수 호출 깊이가 1,000을 넘으면 StackOverflow(E4017) 런타임 에러로 안전하게 중단됩니다.
  • 실행 시간 제한 — 이 IDE는 무한 루프로부터 탭을 보호하기 위해 실행 시간에 상한을 둡니다 (기본 8초, 언제든 직접 중단 가능).
  • 중첩 함수 정의와 클로저는 언어 자체에서 지원하지 않습니다.

관련 링크