Sitelet https://cuffscript.pages.dev/guide/

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"

constant은 list에도 쓸 수 있습니다. 파이썬의 튜플처럼 완전히 읽기 전용인 리스트가 되어, 추가·삭제·인덱스 대입이 전부 런타임 에러입니다 — 다른 변수에 대입하거나 함수 인자로 넘겨도 그 값 자체가 얼어붙어 있으므로 우회할 수 없습니다. 다만 얕은 불변성이라, 얼린 리스트 안에 들어있는 일반 리스트나 맵은 여전히 자유롭게 수정할 수 있습니다. constant map은 아직 지원하지 않습니다.

set constant list PRIMES to [2, 3, 5, 7, 11]

add 13 to PRIMES          note: 런타임 에러 — 얼려진 리스트에는 추가 불가
change PRIMES[1] to 0     note: 이것도 마찬가지로 런타임 에러

add, count, find, split, replace, match, in, by, not, global 같은 문법 단어도 변수·함수·매개변수·반복문 변수의 이름으로 쓸 수 있습니다 (set number count to 3). find처럼 자기만의 구문이 있는 단어는 바로 뒤에 그 구문이 이어질 때만 구문으로, 아니면 평범한 이름으로 취급됩니다.

자료형

타입설명예시
number정수·실수5, 3.14
str문자열"hello"
boolean참/거짓true, false
list1-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: 뒤 실행부가 한 줄에서 끝나면, 같은 줄 끝에 end까지 붙여 들여쓰기 없는 한 줄 축약형으로 쓸 수 있습니다.

한 줄 주석은 note: 내용, 여러 줄 주석은 note: 다음 줄부터 endnote 직전까지입니다.

note: 한 줄 주석
set number x to 10
if x is 10 do: print("통과") end   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. stop은 가장 가까운 반복문 하나만 즉시 종료합니다. 블록형 구문은 들여쓰기가 필수이며, 여는 만큼 end도 정확히 있어야 합니다.

set number score to 85

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
장려
회전 라운드: 1
회전 라운드: 2
회전 라운드: 3

리스트·맵 조작

메서드 대신 자연어 구문으로 컬렉션을 다룹니다: 리스트 끝에 추가는 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 func fib(n) do:
    if n <= 1 do:
        return n
    end
    return fib(n - 1) + fib(n - 2)
end
print(f"fib(10) = {fib(10)}")

중첩 함수 정의와 클로저는 지원하지 않습니다. 함수 안에서 set func를 쓰면 NestedFunctionNotSupported (E4018) 에러가 납니다 — 함수는 항상 최상위에 정의하세요. 함수는 전역 스코프와 자기 자신의 로컬 스코프만 볼 수 있습니다.

returnable, async, pure 세 수식어는 순서 상관없이 자유롭게 조합할 수 있습니다. pure가 붙은 함수는 전역 변수를 읽거나 쓸 수 없습니다 — change 이름 to global 브리지도 예외가 아니며, 시도하는 즉시 PureFunctionGlobalAccess (E4027) 런타임 에러가 발생합니다. 이 제약은 호출을 타고 전파됩니다: pure 함수는 pure가 아닌 사용자 함수를 호출할 수도 없으며, 호출하는 즉시 PureFunctionImpureCall (E4029)가 납니다. 호출되는 쪽도 pure로 표시해야 합니다. (print나 DLC 같은 네이티브 함수는 영향받지 않습니다.)

set number shared_state to 0

set pure returnable func double(x) do:
    return x * 2
end

set pure returnable func quad(x) do:
    return double(double(x))   note: pure가 pure를 부르는 건 OK
end
print(quad(5))   note: 20

set func bump() do:
    change shared_state to global
    change shared_state to shared_state + 1
end

set pure func broken() do:
    bump()   note: E4029 — pure 함수가 pure 아닌 bump()를 호출
end

비동기 (async / await)

async 함수를 await 없이 호출하면 즉시 실행되지 않고 큐에 쌓이며, 최상위 스크립트의 동기 코드가 모두 끝난 뒤 쌓인 순서(FIFO)대로 실행됩니다. await를 붙이면 지금 바로 실행되고 결과값(있다면)을 돌려받습니다.

set async func notify() do:
    print("[비동기] 처리 완료")
end

set async returnable func 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 func 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~1999Lexical (토크나이저)불가능
2000~2999Syntax (파서)불가능
3000~3099Regex Syntax (패턴 컴파일)불가능
3100~3999Regex Runtime (스텝/시간 제한 등)가능
4000~4999Runtime (인터프리터)가능
5000~5999Module (use/from)가능
6000~6999Resource (실행 예산 초과)불가능
9000~9999Internal (엔진 내부 버그)불가능

프로그램 자체가 잘못된 경우(문법 오류 등)는 이미 실행 중인 코드 안에서 나타날 수 없으므로 or_else가 잡지 않으며, "정상적인 코드가 나쁜 상황(0으로 나누기, 없는 파일 등)을 만난 경우"만 or_else로 복구할 수 있습니다. 6000번대는 CLI의 --max-steps/--timeout 실행 예산을 넘었을 때만 발생하며 (기본은 꺼져 있음), "프로그램이 통제를 벗어났다"는 신호이기 때문에 or_else가 일부러 잡지 않도록 설계되어 있습니다.

에러 코드 전체 목록 (71개)
코드범주이름
E1001LexicalUnexpectedCharacter
E1002LexicalUnterminatedString
E1003LexicalInvalidNumberLiteral
E1004LexicalInconsistentIndentation
E1005LexicalColonSpaceBeforeNotAllowed
E1006LexicalUnterminatedComment
E1007LexicalInvalidAssignmentSymbol
E2001SyntaxUnexpectedToken
E2002SyntaxExpectedToken
E2003SyntaxMalformedFunctionDecl
E2004SyntaxMalformedControlFlow
E2005SyntaxMalformedImport
E2006SyntaxUnbalancedBlock
E2007SyntaxInvalidAssignmentTarget
E2008SyntaxNestingTooDeep
E2009SyntaxSourceTooLarge
E3001Regex (문법)RegexUnclosedGroup
E3002Regex (문법)RegexUnclosedBracket
E3003Regex (문법)RegexInvalidColonSpacing
E3004Regex (문법)RegexInvalidQuantifierRange
E3005Regex (문법)RegexStackedQuantifier
E3006Regex (문법)RegexEmptyToken
E3007Regex (문법)RegexUnknownToken
E3008Regex (문법)RegexInvalidEscape
E3009Regex (문법)RegexDanglingQuantifier
E3010Regex (문법)RegexUnexpectedCharacter
E3011Regex (문법)RegexPatternTooComplex
E3101Regex (실행)RegexStepLimitExceeded
E3102Regex (실행)RegexTimeout
E3103Regex (실행)RegexRecursionLimitExceeded
E4001RuntimeUndefinedVariable
E4002RuntimeUndefinedFunction
E4003RuntimeConstantReassignment
E4004RuntimeInvalidConstantName
E4005RuntimeTypeMismatch
E4006RuntimeDivisionByZero
E4007RuntimeZeroIndexAccess
E4008RuntimeIndexOutOfRange
E4009RuntimeKeyNotFound
E4010RuntimeArgumentCountMismatch
E4011RuntimeNotCallable
E4012RuntimeInvalidOperand
E4013RuntimeLocalVariableOutOfScope
E4014RuntimeInvalidCollectionOperation
E4015RuntimeElementNotFound
E4016RuntimeUnsupportedOperation
E4017RuntimeStackOverflow
E4018RuntimeNestedFunctionNotSupported
E4019RuntimeAwaitOnNonAsync
E4020RuntimeDeclarationTypeMismatch
E4021RuntimeInvalidGlobalDeclaration
E4022RuntimeReturnOutsideFunction
E4023RuntimeStopOutsideLoop
E4024RuntimeFractionalIndex
E4025RuntimeInvalidArgumentValue
E4026RuntimeSizeLimitExceeded
E4027RuntimePureFunctionGlobalAccess
E4028RuntimeNetworkRequestFailed
E4029RuntimePureFunctionImpureCall
E5001ModuleModuleNotFound
E5002ModuleModuleParseFailed
E5003ModuleCircularImport
E5004ModuleUnknownDLC
E5005ModuleDLCFeatureUnavailable
E5006ModuleModuleAccessDenied
E5007ModuleModuleLimitExceeded
E5008ModuleFilesystemAccessDenied
E6001ResourceExecutionStepLimit
E6002ResourceExecutionTimeout
E6003ResourceOutOfMemory
E9001InternalInternalError

실제 에러는 아래 코드를 실행하면 이런 형태로 출력됩니다. 에러가 난 소스 줄이 함께 나오고, ^가 정확한 열을 가리킵니다 (앞에 한글이나 이모지 같은 여러 바이트 문자가 있어도 열이 어긋나지 않습니다).

set number a to 10
set number b to 0
print(a / b)   note: or_else가 없으므로 여기서 프로그램이 멈추고 아래 메시지가 출력됨
ERROR: [E4006] Runtime Error at line 3, column 9: division by zero
    print(a / b)
            ^

or_else로 감싸지 않은 런타임 에러는 프로그램을 그 자리에서 멈춥니다. 이 IDE에서는 출력 패널에 위와 같은 메시지가 빨간 글씨로 표시되고, 실행 상태가 "오류"로 바뀝니다.

패턴 매칭 — 기본 사용법과 토큰

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]는 문법 에러).

set str phone to "010-1234-5678"
set str filename to "photo.png"

if phone is "[num]3-[num]4-[num]4" do: print("올바른 번호") end
if filename is "[str]+\.[one:jpg|png|gif]" do: print("이미지 파일") end
올바른 번호
이미지 파일

몇 가지 예시를 더 보면 토큰이 손에 익습니다:

set str color to "#ff00aa"
set str sentence to "my cat sat on the mat"
set str code to "xyz"

note: 16진수 색상 코드 (# 뒤에 hex 문자 6개)
if color is "#[hex]6" do: print("올바른 색상 코드") end

note: [edge]로 단어 경계를 표시 — "cat"은 통과하지만 "category"는 통과하지 않음
if find "[edge]cat[edge]" from sentence is not empty do: print("고양이 언급됨") end

note: [!abc] — 모음이 아닌 문자만 3개 연속
if code is "[!aeiou]3" do: print("모음 없는 3글자") end
올바른 색상 코드
고양이 언급됨
모음 없는 3글자
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 "<year:[num]4>-<month:[num]2>-<day:[num]2>"
print(res["year"])

find는 본문 속 부분 검색입니다. 단독으로는 첫 매칭 문자열 하나를, g 플래그를 붙이면 모든 매칭을 1-Based 리스트로 반환합니다. 매칭이 없으면 empty입니다.

set str article to "접수 번호: T-123, T-456"
set list tickets to find "T-[num]3" from article g
print(tickets[1])
T-123

replace는 패턴에 맞는 부분을 다른 문자열로 바꿉니다. g를 붙이면 전체 치환입니다.

set str phone_log to "통화 기록: 010-1234-5678"
set str masked to replace "[num]4-[num]4" in phone_log to "****-****"
print(masked)
통화 기록: 010-****-****

split은 패턴을 기준으로 문자열을 나눕니다.

set list parts to split "apple, banana,cherry" by ",[sp]*"

count는 패턴이 등장한 횟수를 셉니다. 없으면 0입니다.

set str article to "접수 번호 12번과 34번"
print(count "[num]+" in article)
2

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 func 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: 접두사 없이 바로 호출합니다.

함수 이름은 전부 "라이브러리_동사" 형태입니다 sqrt → math_sqrt, upper → str_upper, get → network_get처럼, 어느 use 줄에서 왔는지 이름만 보고 알 수 있도록 라이브러리 접두어가 붙습니다. 예외는 length/contains/index_of (str/list/map에 걸쳐 의도적으로 동일하게 동작) 와 to_json/from_json/to_number/to_str/to_boolean (이미 이름 자체에 방향이 드러남) 뿐입니다.

전체 함수 목록

엔진에 등록된 모든 내장 함수입니다. 아래 소개에서는 대표적인 것만 예제로 다룹니다.

라이브러리함수
DLC:mathmath_sqrt, math_abs, math_pow, math_round, math_floor, math_ceil, math_trunc, math_sign, math_clamp, math_mod, math_log, math_log10, math_log2, math_atan2, math_pi, math_e, math_exp, math_sin, math_cos, math_tan, math_asin, math_acos, math_atan, math_min, math_max
DLC:stringstr_upper, str_lower, length, contains, index_of, str_starts_with, str_ends_with, str_repeat, str_char_code, str_from_char_code, str_trim, str_trim_start, str_trim_end, str_pad_left, str_pad_right
DLC:timetime_now, time_timestamp
DLC:randomrandom_float, random_int, random_seed, random_choice, random_shuffle
DLC:listlength, contains, index_of, list_sort, list_reverse, list_join, list_unique, list_sum, list_average, list_flatten, list_range
DLC:maplength, contains, map_keys, map_values, map_has_key, map_entries, map_merge
DLC:convertto_number, to_str, to_boolean
DLC:jsonto_json, from_json
DLC:networknetwork_get, network_post
DLC:filesystem
이 브라우저 IDE에서는 차단
file_exist, file_size, file_read, file_readlines, file_write, file_add, file_remove

항상 쓸 수 있는 함수(use 불필요): print, input, type_of, to_number, to_str, to_boolean.

DLC:math

use DLC:math

print(math_sqrt(16))       note: 4
print(math_abs(-5))        note: 5
print(math_pow(2, 10))     note: 1024
print(math_round(3.6))     note: 4
print(math_floor(3.9))     note: 3
print(math_ceil(3.1))      note: 4
print(math_min(4, 1, 9))   note: 1 — 인자를 몇 개든 받을 수 있음
print(math_max(4, 1, 9))   note: 9

DLC:string

use DLC:string

print(str_upper("hello"))              note: "HELLO"
print(str_lower("HELLO"))              note: "hello"
print(str_trim("  padded  "))          note: "padded"
print(length("hello"))                 note: 5 — 접두어 없는 예외
print(contains("hello world", "world"))   note: true — 마찬가지
print(str_starts_with("hello", "he"))  note: true
print(str_ends_with("hello", "lo"))    note: true

DLC:time / DLC:random

use DLC:time
use DLC:random

print(time_now())               note: 유닉스 타임스탬프(초, 실수) — time_timestamp()도 완전히 동일한 함수
set number roll to random_int(1, 6)
print(f"주사위: {roll}")        note: 1~6 사이의 정수, 양 끝 포함
print(random_float())           note: 0.0 이상 1.0 미만의 실수 — 예전 이름은 그냥 random()이었음

DLC:list

원본 리스트를 바꾸지 않고 새 리스트를 반환하는 함수형 스타일입니다. list_sort는 리스트가 전부 숫자이거나 전부 문자열일 때만 동작합니다.

use DLC:list
use DLC:convert

set list scores to [5, 2, 8, 1, 9, 2]
print(list_sort(scores))      note: [1, 2, 2, 5, 8, 9] — scores 자체는 그대로
print(list_reverse(scores))   note: [2, 9, 1, 8, 2, 5]
print(list_unique(scores))    note: [5, 2, 8, 1, 9]

note: list_join()은 문자열 리스트만 받으므로, 숫자는 to_str()로 먼저 변환
set list score_strs to []
loop repeat i to 1 ~ 5 do:
    add to_str(scores[i]) to score_strs
end
print(list_join(score_strs, ", "))   note: "5, 2, 8, 1, 9"

DLC:map

맵은 기본적으로 키를 나열할 방법이 없으므로, 순회가 필요하면 이 라이브러리를 불러옵니다. map_keys/map_values는 맵의 삽입 순서를 그대로 따르는 리스트를 반환합니다.

use DLC:map

set map profile to {"name": "Bob", "level": 12}

print(map_keys(profile))          note: ["name", "level"]
print(map_values(profile))        note: ["Bob", 12]
print(map_has_key(profile, "level"))   note: true

note: loop repeat는 항상 숫자 범위(start ~ end)만 돕니다 — 리스트를 돌 땐
note: 인덱스로 순회합니다. map_entries()는 [키, 값] 쌍의 리스트를 반환합니다.
set list pairs to map_entries(profile)
loop repeat i to 1 ~ length(pairs) do:
    set list entry to pairs[i]
    print(f"{entry[1]} = {entry[2]}")
end

note: map_merge()는 새 맵을 반환 — 키가 겹치면 두 번째 인자가 이깁니다
set map defaults to {"volume": 50, "difficulty": "normal"}
set map overrides to {"difficulty": "hard"}
print(map_merge(defaults, overrides))   note: {"volume": 50, "difficulty": "hard"}

DLC:convert

use DLC:convert

set number n to to_number("42.5")
print(n + 1)              note: 43.5
print(to_str(123) + "!")  note: "123!"
print(to_boolean(""))     note: false — 빈 문자열/0/empty는 false, 그 외 문자열은 true

to_number/to_str/to_boolean은 사실 항상 쓸 수 있는 코어 내장 함수라 use DLC:convert 없이도 바로 호출됩니다. 선언 자체는 문제없이 되고(같은 함수를 다시 등록할 뿐), 어디서 왔는지 코드에 드러내고 싶을 때 명시적으로 씁니다.

DLC:json

use DLC:json

set map user to {"name": "Alice", "level": 7, "active": true}
set str packed to to_json(user)
print(packed)                 note: {"name":"Alice","level":7,"active":true}
print(to_json(user, 2))       note: 들여쓰기 2칸으로 예쁘게 출력

set map parsed to from_json(packed)
print(parsed["name"])         note: "Alice"

JSON의 object/array/string/number/true/false/null은 각각 CuffScript의 map/list/str/number/boolean/empty로 대응됩니다. from_json은 RFC 8259를 엄격히 따르므로 트레일링 콤마, 홑따옴표, 따옴표 없는 키처럼 흔한 변형은 ValueError로 거부됩니다.

DLC:network

network_get(url)과 network_post(url, body[, content_type])이 실제로 동작하는 HTTP/1.1 클라이언트로 연결되어 {"status", "ok", "body"} 형태의 맵을 반환합니다. 평문 HTTP만 지원하며 https://는 조용히 평문으로 격하되지 않고 바로 에러가 됩니다. 접속 실패·DNS 실패·타임아웃은 NetworkRequestFailed (E4028) 런타임 에러로, or_else로 잡을 수 있습니다. 루프백·사설 대역(클라우드 메타데이터 주소 포함)으로의 요청은 SSRF 방지를 위해 기본적으로 차단됩니다. 신뢰할 수 없는 코드를 실행하는 호스트라면 CuffEngine::Options::networkEnabled = false(또는 cuffc --no-network)로 아예 꺼둘 수 있습니다.

use DLC:network

set map res to network_get("http://example.com") or_else do:
    print("요청 실패 — 네트워크가 막혀 있거나 이 IDE 환경의 제약일 수 있음")
    change res to {"status": 0, "ok": false, "body": ""}
end
if res["ok"] do:
    print(res["body"])
end
이 브라우저 IDE에서는? 위 동작은 네이티브 cuffc 기준입니다. 이 페이지의 IDE는 CuffScript를 WebAssembly로 컴파일해 브라우저 탭 안에서 실행하는데, 브라우저는 스크립트가 임의의 TCP 소켓을 직접 여는 것을 애초에 허용하지 않습니다. 그래서 network_get/network_post 호출은 이 IDE에서 대체로 or_else가 잡아야 하는 실패로 끝난다고 보는 게 안전합니다 — 실제 네트워크 요청을 확인하려면 로컬에 빌드한 cuffc로 실행해 보세요.

DLC:filesystem

file_exist, file_size, file_read, file_readlines, file_write, file_add, file_remove로 스크립트 파일 옆의 실제 로컬 파일을 읽고 씁니다. 모든 경로는 use ... from이 모듈을 찾을 때 쓰는 것과 똑같은 샌드박스 루트(스크립트가 있는 폴더, 또는 호스트가 설정한 --root) 안으로 강제되어, 절대 경로나 ../로 벗어나려는 시도는 건드리기도 전에 FilesystemAccessDenied (E5008)로 거부됩니다. 루트 밖을 가리키는 심링크도 거부됩니다. 존재하지 않는 파일을 file_read/file_readlines하면 에러 대신 empty가 돌아옵니다.

use DLC:filesystem

set str path to "notes.txt"

print(file_exist(path))                        note: false
print(file_write(path, "첫째 줄\n둘째 줄"))    note: true
print(file_add(path, "\n셋째 줄"))              note: true — 이어붙이기
print(file_size(path))                          note: 바이트 수
print(file_readlines(path))                     note: ["첫째 줄", "둘째 줄", "셋째 줄"]

print(file_read("no_such_file.txt"))            note: empty — 에러 아님

print(file_remove(path))                        note: true
print(file_exist(path))                         note: false
이 브라우저 IDE에서는 아예 막혀 있습니다 DLC:network와 달리 이건 "대체로 실패"가 아니라 확실한 차단입니다. 이 IDE는 파일 하나하나를 or_else가 잡아야 하는 실패로 두는 대신, use DLC:filesystem이 코드 어디에 있든 실행 자체를 거부하고 바로 에러를 보여줍니다 — 이 페이지의 샌드박스 "파일시스템"은 지금 이 IDE 프로젝트의 다른 탭들이 들어 있는 가상 저장소라서, 실제로 뭔가를 읽고 쓸 수 있게 두면 오히려 혼란스러울 뿐이기 때문입니다. 로컬에 설치한 cuffc에서는 --no-filesystem을 주지 않는 한 정상적으로 동작합니다.
라이브러리 이름이 겹치면? use DLC:list와 use DLC:convert처럼 여러 DLC를 동시에 불러와도 함수 이름이 겹치지 않는 한 문제없이 함께 쓸 수 있습니다. 위 DLC:list 예제에서도 to_str를 쓰기 위해 DLC:convert를 함께 불러왔습니다.

흔히 하는 실수

실수왜 문제인가고치는 법
list[0]인덱스는 1번부터 시작합니다. 0은 존재하지 않는 위치입니다.첫 번째 요소는 list[1].
if x is 5 do :콜론 앞에 공백이 있으면 문법 에러입니다.do:처럼 콜론을 앞 단어에 바로 붙입니다.
set constant number retries to 3상수 이름이 전체 대문자가 아닙니다.RETRIES처럼 대문자로 씁니다.
상수를 change로 수정상수는 선언 이후 값을 바꿀 수 없습니다.애초에 값이 바뀔 변수라면 constant 없이 선언합니다.
함수 안에서 전역 변수를 바로 change함수는 기본적으로 자신만의 로컬 스코프를 가집니다.먼저 change 이름 to global로 선언한 뒤 값을 바꿉니다.
"[num]+"로 정수 전체를 검사했는데 실패is/IS는 부분이 아니라 전체 일치를 검사합니다.문자열 일부만 확인하려면 find를 씁니다.
set number x to input()input()은 항상 str을 반환합니다.to_number(input())처럼 DLC:convert로 변환합니다.
블록을 열고 end를 빠뜨림if/loop/func 등 모든 블록은 여는 만큼 end가 있어야 합니다.들여쓰기를 기준으로 블록이 하나씩 정확히 닫혔는지 확인합니다.

알려진 제한사항

  • 표준 입력 — 실제 키보드 입력이 아니라, stdin 패널에 미리 적어 둔 텍스트를 한 줄씩 소비합니다. 다 소비하면 input()은 빈 문자열을 돌려줍니다.
  • 네트워크 — 네이티브 cuffc에서는 DLC:network가 실제 평문 HTTP 요청을 보냅니다. 이 브라우저 IDE에서는 브라우저 샌드박스가 원시 소켓 접근을 막기 때문에 대체로 실패로 끝납니다 (위 DLC 목록 참고).
  • 파일시스템 — DLC:filesystem은 네이티브 cuffc에서만 쓸 수 있습니다. 이 브라우저 IDE에서는 use DLC:filesystem이 프로젝트의 어느 파일에든 있으면 실행 자체를 거부합니다 (위 DLC 목록 참고).
  • 재귀 깊이 — 함수 호출 깊이가 1,000을 넘으면 StackOverflow(E4017) 런타임 에러로 안전하게 중단됩니다.
  • 실행 시간 제한 — 이 IDE는 무한 루프로부터 탭을 보호하기 위해 실행 시간에 상한을 둡니다 (기본 8초, 언제든 직접 중단 가능).
  • 중첩 함수 정의와 클로저는 언어 자체에서 지원하지 않습니다.
  • 비동기는 동시성이 아닙니다 — async 함수는 OS 스레드로 병렬 실행되지 않고, 최상위 동기 코드가 끝난 뒤 순서대로 실행되는 협력 스케줄링입니다.

관련 링크

여기서 다룬 내용은 IDE의 예제 불러오기 메뉴에 그대로 실행해 볼 수 있는 예제 10개로 준비되어 있습니다. 기초부터 순서대로 따라가며 직접 돌려보는 것을 추천합니다.