본문으로 건너뛰기

진술서

이 섹션에서는 일반 함수 본문의 코드를 구성하는 FunC 문에 대해 간략하게 설명합니다.

표현식 문

가장 일반적인 유형의 문은 표현식 문입니다. 표현식 뒤에 ;가 오는 식입니다. 표현식에 대한 설명은 매우 복잡하므로 여기서는 스케치만 제시합니다. 일반적으로 모든 하위 표현식은 왼쪽에서 오른쪽으로 계산되지만, 순서를 수동으로 정의할 수 있는 asm 스택 재배열을 제외하고는 예외입니다.

변수 선언

초기값을 정의하지 않고 로컬 변수를 선언할 수 없습니다.

다음은 변수 선언의 몇 가지 예입니다:

int x = 2;
var x = 2;
(int, int) p = (1, 2);
(int, var) p = (1, 2);
(int, int, int) (x, y, z) = (1, 2, 3);
(int x, int y, int z) = (1, 2, 3);
var (x, y, z) = (1, 2, 3);
(int x = 1, int y = 2, int z = 3);
[int, int, int] [x, y, z] = [1, 2, 3];
[int x, int y, int z] = [1, 2, 3];
var [x, y, z] = [1, 2, 3];

변수는 동일한 범위에서 "재선언"할 수 있습니다. 예를 들어, 이것은 올바른 코드입니다:

int x = 2;
int y = x + 1;
int x = 3;

사실, int x의 두 번째 발생은 선언이 아니라 x의 타입이 int라는 컴파일 타임 보험에 불과합니다. 따라서 세 번째 줄은 본질적으로 단순한 대입 x = 3;과 동일합니다.

중첩된 범위에서는 C 언어에서와 마찬가지로 변수를 실제로 다시 선언할 수 있습니다. 예를 들어 다음 코드를 생각해 보세요:

int x = 0;
int i = 0;
while (i < 10) {
(int, int) x = (i, i + 1);
;; here x is a variable of type (int, int)
i += 1;
}
;; here x is a (different) variable of type int

그러나 전역 변수 섹션에서 언급했듯이 전역 변수는 다시 선언할 수 없습니다.

변수 선언은 표현식 문장이므로 실제로는 int x = 2와 같은 구조가 본격적인 표현식입니다. 예를 들어, 이것은 올바른 코드입니다:

int y = (int x = 3) + 1;

두 변수 xy를 각각 34로 선언한 것입니다.

밑줄

밑줄 _는 값이 필요하지 않을 때 사용됩니다. 예를 들어 함수 foo의 타입이 int -> (int, int, int)라고 가정해 보겠습니다. 이렇게 하면 첫 번째 반환값을 가져오고 두 번째와 세 번째는 무시할 수 있습니다:

(int fst, _, _) = foo(42);

기능 적용

함수 호출은 기존 언어에서는 이와 같이 보입니다. 함수 호출의 인수는 함수 이름 뒤에 쉼표로 구분하여 나열됩니다.

;; suppose foo has type (int, int, int) -> int
int x = foo(1, 2, 3);

그러나 foo는 실제로 (int, int, int) 타입의 one 인자로 이루어진 함수라는 점에 유의하세요. 차이점을 알아보기 위해 barint -> (int, int, int) 타입의 함수라고 가정해 보겠습니다. 기존 언어와 달리 이렇게 함수를 구성할 수 있습니다:

int x = foo(bar(42));

비슷하지만 더 긴 형태 대신

(int a, int b, int c) = bar(42);
int x = foo(a, b, c);

또한 하스켈 스타일의 호출도 가능하지만 항상 그런 것은 아닙니다(추후 수정 예정):

;; suppose foo has type int -> int -> int -> int
;; i.e. it's carried
(int a, int b, int c) = (1, 2, 3);
int x = foo a b c; ;; ok
;; int y = foo 1 2 3; wouldn't compile
int y = foo (1) (2) (3); ;; ok

람다 표현식

람다 표현식은 아직 지원되지 않습니다.

메서드 호출

비수정 방법

함수에 인자가 하나 이상 있는 경우, 수정되지 않는 메서드로 호출할 수 있습니다. 예를 들어 store_uint의 타입은 (빌더, int, int) -> 빌더입니다(두 번째 인자는 저장할 값이고 세 번째 인자는 비트 길이입니다). begin_cell`은 새 빌더를 생성하는 함수입니다. 다음 코드가 이에 해당합니다:

builder b = begin_cell();
b = store_uint(b, 239, 8);
builder b = begin_cell();
b = b.store_uint(239, 8);

따라서 함수의 첫 번째 인수는 함수 이름 앞에 .로 구분하여 전달할 수 있습니다. 코드를 더 단순화할 수 있습니다:

builder b = begin_cell().store_uint(239, 8);

메서드를 여러 번 호출할 수도 있습니다:

builder b = begin_cell().store_uint(239, 8)
.store_int(-1, 16)
.store_uint(0xff, 10);

방법 수정

함수의 첫 번째 인자가 A 타입이고 함수의 반환값이 (A, B) 형태이고 B가 임의의 타입인 경우, 함수를 수정 메서드로 호출할 수 있습니다. 수정 메서드 호출은 일부 인수를 받아 일부 값을 반환할 수 있지만, 첫 번째 인수를 수정하는 즉, 반환된 값의 첫 번째 구성 요소를 첫 번째 인수에서 변수에 할당합니다. 예를 들어 cs가 셀 슬라이스이고 load_uint의 타입이 (슬라이스, int) -> (슬라이스, int)라고 가정하면, 이 함수는 로드할 셀 슬라이스와 비트 수를 취하고 나머지 슬라이스와 로드된 값을 반환합니다. 다음 코드가 이에 해당합니다:

(cs, int x) = load_uint(cs, 8);
(cs, int x) = cs.load_uint(8);
int x = cs~load_uint(8);

어떤 경우에는 값을 반환하지 않고 첫 번째 인수만 수정하는 수정 메서드로 함수를 사용하고 싶을 때가 있습니다. 단위 타입을 사용하면 다음과 같이 할 수 있습니다: 정수를 증가시키는 int -> int 타입의 inc 함수를 정의하고 이를 수정 메서드로 사용한다고 가정해 보겠습니다. 그런 다음 incint -> (int, ()) 타입의 함수로 정의해야 합니다:

(int, ()) inc(int x) {
return (x + 1, ());
}

이렇게 정의하면 수정 메서드로 사용할 수 있습니다. 다음은 x를 증가시킵니다.

x~inc();

함수 이름에 .~ 사용

수정하지 않는 메서드로 inc를 사용한다고 가정해 보겠습니다. 이렇게 작성할 수 있습니다:

(int y, _) = inc(x);

그러나 수정 메서드로서 inc의 정의를 재정의할 수 있습니다.

int inc(int x) {
return x + 1;
}
(int, ()) ~inc(int x) {
return (x + 1, ());
}

그리고 그렇게 전화하세요:

x~inc();
int y = inc(x);
int z = x.inc();

첫 번째 호출은 x를 수정하지만 두 번째와 세 번째 호출은 수정하지 않습니다.

요약하면, 이름이 foo인 함수가 비수정 또는 수정 메서드로 호출될 때(즉, .foo 또는 ~foo 구문으로), FunC 컴파일러는 해당 정의가 제시되면 그에 해당하는 '.foo' 또는 ~foo의 정의를 사용하고, 그렇지 않으면 foo의 정의를 사용합니다.

연산자

현재 단항 연산자와 이진 연산자는 모두 정수 연산자입니다. 논리 연산자는 비트 단위 정수 연산자로 표시됩니다(참조: 부울 유형 부재).

단항 연산자

단항 연산자는 두 가지가 있습니다:

  • ~는 비트 단위로 아닙니다(우선순위 75).
  • -는 정수 부정(우선순위 20)입니다.

인수는 인수와 분리되어야 합니다:

  • X`는 괜찮습니다.
  • x`는 괜찮지 않습니다(단일 식별자입니다).

이진 연산자

우선순위 30(왼쪽 연관):

  • *는 정수 곱셈입니다.
  • /는 정수 나누기(바닥)입니다.
  • ~/는 정수 나누기(반올림)입니다.
  • ^/는 정수 나누기(상한)입니다.
  • %는 모듈로 정수 환원(바닥)입니다.
  • ~%는 모듈로 정수 환원(반올림)입니다.
  • '^%는 모듈로 정수 환원(상한)입니다.
  • /%는 몫과 나머지를 반환합니다.
  • &는 비트 AND

우선순위 20(왼쪽 연관):

  • +는 정수 덧셈입니다.
  • -는 정수 빼기입니다.
  • |는 비트 또는
  • ^는 비트 단위 XOR

우선순위 17(왼쪽 연관):

  • <<는 비트 왼쪽 시프트
  • >>는 비트 단위 오른쪽 이동입니다.
  • ~>>는 비트 단위 오른쪽 이동(라운드)입니다.
  • ^>>는 비트 단위 오른쪽 이동(천정)입니다.

우선순위 15(왼쪽 연관):

  • ==는 정수 동일성 검사입니다.
  • '!='는 정수 부등식 검사입니다.
  • <는 정수 비교입니다.
  • <=는 정수 비교입니다.
  • >는 정수 비교입니다.
  • >=는 정수 비교입니다.
  • <=>`는 정수 비교(-1, 0 또는 1 반환)입니다.

또한 인수를 인수와 분리해야 합니다:

  • 'x + y'는 괜찮습니다.
  • 'x+y'는 괜찮지 않습니다(단일 식별자).

조건부 연산자

일반적인 구문을 사용합니다.

<condition> ? <consequence> : <alternative>

예를 들어

x > 0 ? x * fac(x - 1) : 1;

우선 순위는 13입니다.

과제

우선순위 10.

단순 대입 =와 이진 연산의 대응 연산: +=, -=, *=, /=, ~/=, ^/=, %=, ~%=, ^%=, <<=, >>=, ~>>=, ^>>=, &=, |=, ^=.

루프

FunC는 repeat, while, do { ... } until 루프를 지원합니다. for` 루프는 지원되지 않습니다.

반복 루프

구문은 repeat 키워드 뒤에 int 타입의 표현식이 오는 방식입니다. 지정된 횟수만큼 코드를 반복합니다. 예제:

int x = 1;
repeat(10) {
x *= 2;
}
;; x = 1024
int x = 1, y = 10;
repeat(y + 6) {
x *= 2;
}
;; x = 65536
int x = 1;
repeat(-1) {
x *= 2;
}
;; x = 1

횟수가 -2^31보다 작거나 2^31 - 1보다 크면 범위 확인 예외가 발생합니다.

동안 루프

일반적인 구문을 사용합니다. 예시:

int x = 2;
while (x < 100) {
x = x * x;
}
;; x = 256

조건 x < 100의 진리 값은 int 유형입니다(참조: 부울 유형 부재).

루프까지

구문은 다음과 같습니다:

int x = 0;
do {
x += 3;
} until (x % 17 == 0);
;; x = 51

If 문

예시:

;; usual if
if (flag) {
do_something();
}
;; equivalent to if (~ flag)
ifnot (flag) {
do_something();
}
;; usual if-else
if (flag) {
do_something();
}
else {
do_alternative();
}
;; Some specific features
if (flag1) {
do_something1();
} else {
do_alternative4();
}

중괄호는 필수입니다. 해당 코드는 컴파일되지 않습니다:

if (flag1)
do_something();

Try-Catch 문

v0.4.0부터 func에서 사용 가능.

try블록의 코드를 실행합니다. 실패하면try블록의 변경 사항을 완전히 롤백하고 대신catch블록을 실행합니다.catch는 임의의 유형의 예외 매개변수(x)와 오류 코드(n`, 정수) 두 가지 인자를 받습니다.

FunC try-catch 문에서 다른 많은 언어와 달리 시도 블록의 변경 사항, 특히 로컬 및 전역 변수의 수정은 시도 블록에 오류가 발생하면 모든 레지스터의 변경 사항(예: c4 스토리지 레지스터, c5 액션/메시지 레지스터, c7 컨텍스트 레지스터 등)이 버려지고 결과적으로 모든 컨트랙트 스토리지 업데이트와 메시지 전송이 되돌려지게 됩니다. 코드페이지_ 및 가스 카운터와 같은 일부 TVM 상태 매개변수는 롤백되지 않는다는 점에 유의해야 합니다. 이는 특히 시도 블록에서 사용된 모든 가스가 고려되며, 가스 한도를 변경하는 OP(accept_messageset_gas_limit)의 효과는 보존된다는 것을 의미합니다.

예외 매개변수는 어떤 유형이든 될 수 있으므로(다른 예외의 경우 다른 유형일 수도 있음), 컴파일 타임에 funC가 이를 예측할 수 없습니다. 즉, 개발자가 예외 매개변수를 특정 유형으로 캐스팅하여 컴파일러를 "도와줘야" 합니다(아래 예제 2 참조):

예시:

try {
do_something();
} catch (x, n) {
handle_exception();
}
forall X -> int cast_to_int(X x) asm "NOP";
...
try {
throw_arg(-1, 100);
} catch (x, n) {
x.cast_to_int();
;; x = -1, n = 100
return x + 1;
}
int x = 0;
try {
x += 1;
throw(100);
} catch (_, _) {
}
;; x = 0 (not 1)

블록 문

블록 문도 허용됩니다. 새 중첩된 범위가 열립니다:

int x = 1;
builder b = begin_cell();
{
builder x = begin_cell().store_uint(0, 8);
b = x;
}
x += 1;