MyLang은 Python
, JavaScript
및 C
에서 영감을 받은 간단한 교육용 프로그래밍 언어로, 주로 재귀 하강 파서를 작성하고 인터프리터의 세계를 탐험하기 위해 짧은 시간 안에 개인적인 도전으로 작성되었습니다. 프로덕션용으로 준비된 라이브러리와 프레임워크를 갖춘 본격적인 스크립팅 언어를 기대하지 마십시오. 그러나 MyLang
에는 최소한의 내장 기능 세트가 있으므로 실용적인 목적으로도 사용할 수 있습니다 .
MyLang은 이식 가능한 C++17로 작성되었습니다. 현재 프로젝트에는 표준 C++ 라이브러리 외에는 종속성이 없습니다. 빌드하려면 GNU make
설치되어 있는 경우 다음을 실행하세요.
$ make -j
그렇지 않으면 모든 .cpp 파일을 컴파일러에 전달하고 src/
디렉터리를 포함 검색 경로에 추가하면 됩니다. 종속성을 갖지 않는 것의 가장 좋은 점 중 하나는 일회성 빌드를 위한 빌드 시스템이 필요하지 않다는 것입니다.
BUILD_DIR
옵션을 전달하여 다음 make
.
$ make -j BUILD_DIR=other_build_directory
MyLang의 테스트도 실행하려면 더 나은 디버깅 환경을 위해 TESTS=1로 컴파일하고 OPT=0으로 최적화를 비활성화하면 됩니다.
$ make -j TESTS=1 OPT=0
그런 다음 다음을 사용하여 모든 테스트를 실행합니다.
$ ./build/mylang -rt
GoogleTest 및 Boost.Test와 같은 테스트 프레임워크는 src/tests.cpp
에 있는 간단한 테스트 엔진보다 훨씬 더 강력하고 유연하지만 외부 종속성이라는 점에 주목할 가치가 있습니다. 의존성이 적을수록 좋습니다. 그렇죠? :-)
MyLang
설명하는 가장 짧은 방법은 C처럼 보이는 동적 Python 같은 언어 입니다. 아마도 이 언어를 배우는 가장 빠른 방법은 아래의 짧은 문서를 살펴보면서 samples/
디렉토리에 있는 스크립트를 확인하는 것입니다.
MyLang
은 Python
과 같은 동적 오리 타이핑 언어입니다. Python
알고 있고 { }
중괄호를 사용하려는 경우 자동으로 사용할 수 있습니다. 놀라운 일이 아닙니다. 문자열은 Python
처럼 불변이고, 배열은 Python
처럼 [ ]
사용하여 정의할 수 있으며, 사전도 { }
사용하여 정의할 수 있습니다. 이 언어는 Python
에서 사용하는 것과 동일한 [start:end]
구문을 사용하여 배열 슬라이스도 지원합니다.
MyLang
여러 측면에서 Python
및 기타 스크립트 언어와 다릅니다.
const
사용하여 선언된 구문 분석 시간 상수가 지원됩니다.
모든 변수는 var
사용하여 선언 해야 합니다 .
변수에는 C
와 같은 범위가 있습니다. 중첩된 블록에서 var
사용하여 변수를 명시적으로 다시 선언하는 경우 섀도잉이 지원됩니다.
모든 표현식 문은 ;
로 끝나야 합니다. C
, C++
및 Java
와 같습니다.
true
및 false
키워드가 존재하지만 boolean
유형은 없습니다. C와 마찬가지로 0
은 false이고 나머지는 모두 true
입니다. 그러나 문자열, 배열 및 사전은 Python
과 정확히 같은 부울 값을 갖습니다(예: 빈 배열은 false
간주됩니다). true
내장은 정수 1
의 별칭일 뿐입니다.
할당 연산자 =
는 C
에서와 같이 표현식 내부에서 사용할 수 있지만 배열 확장 기능으로 인해 쉼표 연산자와 같은 것은 없습니다.
MyLang은 클래식 for
루프와 명시적인 foreach
루프를 모두 지원합니다.
MyLang은 현재 사용자 정의 유형을 지원하지 않습니다. 그러나 사전은 멋진 구문 설탕을 지원합니다. 기본 구문 d["key"]
외에도 문자열 키의 경우 d.key
구문도 지원됩니다.
변수는 항상 var
로 선언되며 선언된 범위에 있습니다(중첩된 범위에서는 표시됨). 예를 들어:
# Variable declared in the global scope
var a = 42 ;
{
var b = 12 ;
# Here we can see both `a` and `b`
print ( a , b ) ;
}
# But here we cannot see `b` .
다음과 같은 친숙한 구문을 사용하여 여러 변수를 선언할 수 있습니다.
var a , b , c ;
그러나 MyLang
의 유일한 "놀라운" 기능인 주의 사항이 있습니다. 변수 초기화는 C에서처럼 작동하지 않습니다. 다음 설명을 고려하세요.
var a , b , c = 42 ;
이 경우 a
와 b
선언하고 c
42로 초기화하는 대신 세 변수를 모두 값 42로 초기화합니다. 각 변수를 다른 값으로 초기화하려면 배열 확장 구문을 사용합니다.
var a , b , c = [ 1 , 2 , 3 ] ;
상수는 변수 와 비슷한 방식으로 선언되지만 중첩된 범위에서 숨길 수는 없습니다. 예를 들어:
const c = 42 ;
{
# That's not allowed
const c = 1 ;
# That's not allowed as well
var c = 99 ;
}
MyLang
에서 상수는 C++
의 constexpr 선언과 유사한 방식으로 구문 분석 시 평가됩니다(하지만 여기서는 컴파일 시간 에 대해 이야기합니다). const
초기화하는 동안 전체 const 내장 세트 외에도 모든 종류의 리터럴을 사용할 수 있습니다. 예를 들어:
const val = sum ( [ 1 , 2 , 3 ] ) ;
const x = " hello " + " world " + " " + join ( [ " a " , " b " , " c " ] , " , " ) ;
상수가 얼마나 정확하게 평가되었는지 이해하려면 -s
옵션과 함께 인터프리터를 실행하여 스크립트를 실행하기 전에 추상 구문 트리를 덤프하십시오. 위의 예에서는 다음과 같습니다.
$ cat > t
const val = sum([1,2,3]);
const x = "hello" + " world" + " " + join(["a","b","c"], ",");
$ ./build/mylang t
$ ./build/mylang -s t
Syntax tree
--------------------------
Block(
)
--------------------------
놀란? 글쎄, 배열과 사전 이외의 상수는 변수로 인스턴스화되지도 않습니다. 런타임 에는 존재하지 않습니다. x
사용하여 명령문을 추가해 보겠습니다.
$ cat >> t
print(x);
$ cat t
const val = sum([1,2,3]);
const x = "hello" + " world" + " " + join(["a","b","c"], ",");
print(x);
$ ./build/mylang -s t
Syntax tree
--------------------------
Block(
CallExpr(
Id("print")
ExprList(
"hello world a,b,c"
)
)
)
--------------------------
hello world a,b,c
이제 모든 것이 이해가 될 것입니다. 배열과 사전에서도 거의 동일한 일이 발생합니다. 단, 후자는 런타임 시와 마찬가지로 인스턴스화되어 잠재적으로 큰 리터럴이 어디에나 있는 것을 방지합니다. 다음 예를 고려하십시오.
$ ./build/mylang -s -e 'const ar=range(4); const s=ar[2:]; print(ar, s, s[0]);'
Syntax tree
--------------------------
Block(
ConstDecl(
Id("ar")
Op '='
LiteralArray(
Int(0)
Int(1)
Int(2)
Int(3)
)
)
ConstDecl(
Id("s")
Op '='
LiteralArray(
Int(2)
Int(3)
)
)
CallExpr(
Id("print")
ExprList(
Id("ar")
Id("s")
Int(2)
)
)
)
--------------------------
[0, 1, 2, 3] [2, 3] 2
보시다시피, 슬라이스 작업은 상수 s
초기화하는 동안 구문 분석 시 평가되었지만 두 배열 모두 런타임에도 존재합니다. 대신 const 표현식에 대한 아래 첨자 연산은 리터럴로 변환됩니다. 이는 성능에 대한 좋은 절충점처럼 보입니다. 정수, 부동 소수점 및 문자열과 같은 작은 값은 const 평가 중에 리터럴로 변환되는 반면, 배열 및 사전(잠재적으로 클 수 있음)은 런타임에 읽기 전용 기호로 남아 있지만 여전히 허용됩니다. 이에 대한 일부 작업(예: [index]
및 len(arr)
)은 const 평가됩니다.
MyLang
현재 다음(내장) 유형만 지원합니다.
None none
유형으로, Python의 None
과 동일합니다. 할당된 값 없이 방금 선언된 변수는 값이 none
(예: var x;
)입니다. 반환 값이 없는 함수에도 동일하게 적용됩니다. 또한, 실패할 경우 find()
와 같은 내장 기능에서 특수 값으로 사용됩니다.
정수 부호 있는 포인터 크기 정수(예: 3
).
Float 부동 소수점 숫자(예: 1.23
)입니다. 내부적으로는 긴 더블입니다.
String "hello"와 같은 문자열입니다. 문자열은 불변이며 슬라이스를 지원합니다(예: s[3:5]
또는 s[3:]
또는 s[-2:]
, Python
에서와 동일한 의미를 가짐).
Array 배열과 튜플에 대한 변경 가능한 유형입니다(예 [1,2,3]
). 다양한 유형의 항목을 포함할 수 있으며 쓰기 가능한 슬라이스를 지원합니다. 배열 슬라이스는 복사본처럼 동작하지만 내부적으로는 쓰기 시 복사 기술을 사용합니다.
사전 사전은 Python
의 구문: {"a": 3, "b": 4}
사용하여 정의된 해시 맵입니다. 요소는 익숙한 구문 d["key-string"]
또는 d[23]
을 사용하여 액세스하고, find()
로 조회하고, erase()
로 삭제할 수 있습니다. 현재로서는 문자열, 정수 및 부동 소수점만 사전의 키로 사용할 수 있습니다. 장점 : 식별자와 유사한 문자열 키는 "member of" 구문( d.key
사용하여 액세스할 수도 있습니다.
함수 독립형 함수와 람다 모두 동일한 객체 유형을 가지며 다른 객체처럼 전달될 수 있습니다(아래 참조). 그러나 람다만 캡처 목록을 가질 수 있습니다. 일반 함수는 const 평가 중에 실행할 수 없지만 pure
함수는 실행할 수 있습니다. 순수 함수는 const와 해당 인수만 볼 수 있습니다.
Exception 발생할 수 있는 유일한 개체 유형입니다. 이를 생성하려면 내장된 exception()
또는 바로 가기인 ex()
를 사용하십시오.
조건문은 C
에서와 똑같이 작동합니다. 구문은 다음과 같습니다.
if ( conditionExpr ) {
# Then block
} else {
# Else block
}
단일 명령문 블록의 경우 C
에서처럼 { }
중괄호를 생략할 수 있습니다. conditionExpr
임의의 표현식일 수 있습니다(예: (a=3)+b >= c && !d
.
conditionExpr
이 const 평가될 수 있는 표현식인 경우 전체 if 문은 true 분기로 대체되고 false 분기는 삭제됩니다. 예를 들어 다음 스크립트를 고려해보세요.
const a = 3 ;
const b = 4 ;
if ( a < b ) {
print ( " yes " ) ;
} else {
print ( " no " ) ;
}
항상 "yes"를 인쇄할 뿐만 아니라 이를 수행하기 전에 아무것도 확인할 필요도 없습니다. 추상 구문 트리를 확인하세요.
$ ./build/mylang -s t
Syntax tree
--------------------------
Block(
Block(
CallExpr(
Id("print")
ExprList(
"yes"
)
)
)
)
--------------------------
yes
MyLang
고전적인 while
및 for
루프를 지원합니다.
while ( condition ) {
# body
if ( something )
break ;
if ( something_else )
continue ;
}
for ( var i = 0 ; i < 10 ; i += 1 ) {
# body
if ( something )
break ;
if ( something_else )
continue ;
}
여기서는 위의 경우처럼 { }
중괄호를 생략할 수 있습니다. C
와는 몇 가지 차이점만 지적할 가치가 있습니다.
현재 MyLang
에는 ++
및 --
연산자가 없습니다.
여러 변수를 선언하려면 다음 구문을 사용합니다. var a, b = [3,4];
아니면 그냥 var a,b,c,d = 0;
모든 변수가 동일한 초기 값을 갖기를 원하는 경우.
여러 변수의 값을 늘리려면 a, b += [1, 2]
구문을 사용합니다. for 루프의 증분 문에서 서로 다른 표현식을 사용하여 각 변수에 새 변수를 할당해야 하는 매우 드물고 복잡한 경우 할당 시 확장 구문을 활용하세요. i, j = [i+2, my_next(i, j*3)]
.
MyLang
매우 친숙한 구문을 사용하여 foreach
루프를 지원합니다.
var arr = [ 1 , 2 , 3 ] ;
foreach ( var e in arr ) {
print ( " elem: " , e ) ;
}
Foreach 루프는 배열, 문자열 및 사전에 사용할 수 있습니다. 예를 들어 사전의 각 <key, value>
쌍을 반복하는 것은 다음과 같이 쉽습니다.
var d = { " a " : 3 , " b " : 10 , " c " : 42 } ;
foreach ( var k , v in d ) {
print ( k + " => " + str ( v ) ) ;
}
각 키를 통해서만 반복하려면 대신 var k in d
사용하세요.
MyLang
foreach 루프에서도 열거를 지원합니다. 다음 예를 확인하세요.
var arr = [ " a " , " b " , " c " ] ;
foreach ( var i , elem in indexed arr ) {
print ( " elem[ " + str ( i ) + " ] = " + elem ) ;
}
즉, 컨테이너 이름 앞에 indexed
키워드가 오면 첫 번째 변수는 각 반복마다 누진 번호가 할당됩니다.
작은 고정 크기 배열(튜플에 대해 생각해 보세요)의 배열을 반복하는 동안 foreach 루프에서 해당 "튜플"을 직접 확장할 수 있습니다.
var arr = [
[ " hello " , 42 ] ,
[ " world " , 11 ]
] ;
foreach ( var name , value in arr ) {
print ( name , value ) ;
}
# This is a shortcut for :
foreach ( var elem in arr ) {
# regular array expansion
var name , value = elem ;
print ( name , value ) ;
}
# Which is a shortcut for :
foreach ( var elem in arr ) {
var name = elem [ 0 ] ;
var value = elem [ 1 ] ;
print ( name , value ) ;
}
함수 선언은 다음과 같이 간단합니다.
func add ( x , y ) {
return x + y ;
}
그러나 여러 단축키도 지원됩니다. 예를 들어 위와 같은 단일 명령문 함수의 경우 다음 구문을 사용할 수 있습니다.
func add ( x , y ) => x + y ;
또한 매개변수가 없는 함수에 대해 항상 ()
작성하는 것이 좋은 습관이지만 실제로 이 언어에서는 선택 사항입니다.
func do_something { print ( " hello " ) ; }
MyLang
에서는 함수가 일반 기호로 처리되며 이 언어의 독립형 함수와 람다 간에는 큰 차이가 없습니다. 예를 들어, 다음과 같이 add
함수(위)를 람다로 선언할 수 있습니다.
var add = func ( x , y ) => x + y ;
참고: 표현식에서 함수 객체를 생성할 때 이름을 할당할 수 없습니다.
Lambda는 캡처 목록도 지원하지만 명확성을 강화하기 위해 암시적 캡처는 지원되지 않습니다. 물론 람다는 다른 객체로 반환될 수 있습니다. 예를 들어:
func create_adder_func ( val ) =>
func [ val ] ( x ) => x + val ;
var f = create_adder_func ( 5 ) ;
print ( f ( 1 ) ) ; # Will print 6
print ( f ( 10 ) ) ; # Will print 15
누구나 예상할 수 있듯이 캡처가 포함된 람다는 상태를 갖습니다. 다음 스크립트를 고려해보세요.
func gen_counter ( val ) => func [ val ] {
val += 1 ;
return val ;
} ;
var c1 = gen_counter ( 5 ) ;
for ( var i = 0 ; i < 3 ; i += 1 )
print ( " c1: " , c1 ( ) ) ;
# Clone the `c1` lambda object as `c2` : now it will have
# its own state , indipendent from `c1` .
var c2 = clone ( c1 ) ;
print ( ) ;
for ( var i = 0 ; i < 3 ; i += 1 )
print ( " c2: " , c2 ( ) ) ;
print ( ) ;
for ( var i = 0 ; i < 3 ; i += 1 )
print ( " c1: " , c1 ( ) ) ;
출력을 생성합니다.
c1: 6
c1: 7
c1: 8
c2: 9
c2: 10
c2: 11
c1: 9
c1: 10
c1: 11
일반 사용자 정의 함수 개체(람다 포함)는 const
간주되지 않으므로 const 평가 중에 실행할 수 없습니다 . 그것은 꽤 강력한 제한입니다. 다음 예를 고려하십시오.
const people = [
[ " jack " , 3 ] ,
[ " alice " , 11 ] ,
[ " mario " , 42 ] ,
[ " bob " , 38 ]
] ;
const sorted_people = sort ( people , func ( a , y ) => a [ 0 ] < b [ 0 ] ) ;
이 경우 스크립트는 sorted_people
const 배열을 생성 할 수 없습니다 . 함수 객체를 const sort()
내장에 전달했기 때문에 ExpressionIsNotConstEx
오류가 발생합니다. 물론, sorted_people
var
로 선언되면 스크립트가 실행되지만 배열은 더 이상 const가 아니므로 구문 분석 시간 최적화의 이점을 얻을 수 없습니다. 따라서 const 평가 중에 sort()
내장 기능을 호출할 수 있지만 사용자 정의 compare func
매개변수가 있는 경우 더 이상 불가능합니다.
방금 설명한 한계를 극복하기 위해 MyLang
에는 순수 함수에 대한 특별한 구문이 있습니다. func
앞에 pure
키워드를 사용하여 함수를 선언하면 인터프리터는 함수를 특별한 방식으로 처리합니다. const 평가 중과 런타임 중 언제든지 호출 할 수 있지만 함수 는 전역 변수를 볼 수도 없고 아무것도 캡처할 수도 없습니다. 상수와 해당 매개변수의 값: 이것이 바로 const 평가 중에 필요한 것입니다. 예를 들어, const 평가 중에 sorted_people
생성하려면 다음과 같이 작성하면 충분합니다.
const sorted_people = sort ( people , pure func ( a , b ) => a [ 0 ] < b [ 0 ] ) ;
순수 함수는 독립형 함수로 정의할 수 있으며 const가 아닌 매개변수와 함께 사용할 수도 있습니다 . 그러므로 함수가 pure
로 선언될 수 있다면 항상 그 방식으로 선언해야 합니다. 예를 들어 다음 스크립트를 고려해보세요.
pure func add2 ( x ) => x + 2 ;
var non_const = 25 ;
print ( add2 ( non_const ) ) ;
print ( add2 ( 5 ) ) ;
언어 엔진이 런타임에 사용할 추상 구문 트리는 다음과 같습니다.
$ ./build/mylang -s t
Syntax tree
--------------------------
Block(
FuncDeclStmt(
Id("add2")
<NoCaptures>
IdList(
Id("x")
)
Expr04(
Id("x")
Op '+'
Int(2)
)
)
VarDecl(
Id("non_const")
Op '='
Int(25)
)
CallExpr(
Id("print")
ExprList(
CallExpr(
Id("add2")
ExprList(
Id("non_const")
)
)
)
)
CallExpr(
Id("print")
ExprList(
Int(7)
)
)
)
--------------------------
27
7
보시다시피, 첫 번째 경우에는 non_const
상수가 아니기 때문에 실제 함수 호출이 발생하는 반면, 두 번째 경우에는 리터럴 정수를 print()
에 전달한 AS IF입니다.
다른 구성과 마찬가지로 MyLang
에는 Python
과 유사하지만 C++
와 유사한 구문을 사용하는 예외 처리 기능이 있습니다. 기본 구성은 try-catch
문입니다. 예를 살펴보겠습니다:
try {
var input_str = " blah " ;
var a = int ( input_str ) ;
} catch ( TypeErrorEx ) {
print ( " Cannot convert the string to integer " ) ;
}
참고: 상수 표현식(예: int("blah")
)에 의해 예외가 생성되면 const 평가 중에 오류가 예외 처리 논리를 우회하고 직접 보고됩니다. 그 이유는 조기 실패를 강제하기 위함이다.
여러 개의 catch
문도 허용됩니다.
try {
# body
} catch ( TypeErrorEx ) {
# error handling
} catch ( DivisionByZeroEx ) {
# error handling
}
그리고 동일한 코드로 여러 예외를 처리할 수 있는 경우 더 짧은 구문도 사용할 수 있습니다.
try {
# body
} catch ( TypeErrorEx , DivisionByZeroEx as e ) {
# error handling
print ( e ) ;
} catch ( OutOfBoundsEx ) {
# error handling
}
예외에는 데이터가 포함될 수 있지만 현재 내장된 예외에는 데이터가 포함되어 있지 않습니다. try-catch
블록으로 포착할 수 있는 내장 런타임 예외 목록은 다음과 같습니다.
대신 SyntaxErrorEx
와 같은 다른 예외는 포착될 수 없습니다. MyLang
에서는 catch-anything 블록을 사용하여 모든 예외를 잡는 것도 가능합니다.
try {
# body
} catch {
# Something went wrong .
}
이 언어는 현재 사용자 정의 유형을 지원하지 않습니다. 따라서 다른 언어처럼 어떤 종류의 객체도 던질 수 없습니다. 예외를 발생시키려면 특수 내장 함수인 exception()
또는 단축 함수인 ex()
사용해야 합니다. 다음 예를 고려하십시오.
try {
throw ex ( " MyError " , 1234 ) ;
} catch ( MyError as e ) {
print ( " Got MyError, data: " , exdata ( e ) ) ;
}
직관에서 알 수 있듯이 ex()
사용하여 페이로드 데이터가 1234
인 MyError
라는 예외 개체를 생성하고 나중에 발생시켰습니다. 나중에 catch
블록에서 예외를 포착하고 exdata()
내장 기능을 사용하여 페이로드 데이터를 추출했습니다.
특정 예외에 페이로드가 필요하지 않은 경우 ex()
의 결과를 변수에 저장하고 나중에 좀 더 편리한 구문을 사용하여 던질 수 있습니다.
var MyError = ex ( " MyError " ) ;
throw MyError ;
MyLang
전용 rethrow
키워드를 사용하여 catch 문의 본문에서 예외 다시 발생을 지원합니다.
try {
do_something ( ) ;
} catch {
print ( " Something went wrong!! " ) ;
rethrow ;
}
어떤 경우에는 예외를 발생시킬 수 있는 코드 블록을 실행한 후 정리 작업을 수행해야 할 수도 있습니다. 이러한 경우 MyLang
C#
에서와 동일하게 작동하는 잘 알려진 finally
절을 지원합니다.
try {
step1_might_throw ( ) ;
step2_might_throw ( ) ;
step3_might_throw ( ) ;
step4_might_throw ( ) ;
} catch ( TypeErrorEx ) {
# some error handling
} finally {
# clean - up
}
try-finally
구문( catch
절 없이)도 허용된다는 점은 주목할 가치가 있습니다.
다음 내장 함수는 const 인수가 전달될 때 구문 분석 중에 평가됩니다.
defined(symbol)
symbol
정의되어 있는지 확인하세요. 기호가 정의되어 있으면 1을 반환하고, 그렇지 않으면 0을 반환합니다.
len(container)
주어진 컨테이너의 요소 수를 반환합니다.
str(value, [decimal_digits])
주어진 값을 문자열로 변환합니다. value
이 부동 소수점인 경우 두 번째 매개변수는 출력 문자열에서 원하는 소수 자릿수를 나타냅니다.
int(value)
주어진 문자열을 정수로 변환합니다. 값이 부동소수점인 경우 잘립니다. 값이 문자열이면 가능한 경우 구문 분석되어 정수로 변환됩니다. 값이 이미 정수이면 그대로 반환됩니다.
float(value)
주어진 값을 float로 변환합니다. 값이 정수이면 부동 소수점 숫자로 변환됩니다. 값이 문자열이면 가능한 경우 구문 분석되어 부동 소수점으로 변환됩니다. 값이 이미 부동 소수점인 경우 그대로 반환됩니다.
clone(obj)
주어진 객체를 복제합니다. 캡처 기능이 있는 배열, 사전 및 람다와 같은 중요하지 않은 개체에 유용합니다.
type(value)
주어진 값의 유형 이름을 문자열 형식으로 반환합니다. 디버깅에 유용합니다.
hash(value)
value
키로 사용될 때 사전이 내부적으로 사용하는 해시 값을 반환합니다. 현재는 정수, 부동 소수점 및 문자열만 hash()
지원합니다.
array(N)
none
배열을 반환합니다.