Blockly Code generation

양석우·2023년 2월 11일
0

Blockly

목록 보기
3/5

저번 포스트에서 custom 블록의 모양과 성질을 정의하는 방법을 배웠다.
이제 겉은 번지르르한 블록이 완성된 것 !
하지만, 여태까지 만든 블록은 형태만 갖춰진 껍데기이기 때문에 실질적인 기능을 하지는 못한다.

블록이 실제 프로그래밍 언어로 된 코드와 연결될 수 있게 하기 위해서는 code generator라는 특수한코드를 같이 붙여줘야 한다!

보통 code generator는 block definiton 부분과 별도의 .js파일로 분리하여 작성하게 된다.

Code generator는 Javascript로 정의한다.
블록 정의는 2가지 방법으로 가능하다하지 않았나?
code generator 정의는 JavaScript 스타일로만 정의가 가능하다.
또한 블록을 변환할 언어(목표 언어)에 따라 실제 매핑되는 구문이 달라져야 하기 때문에, 코드 작성 시 어떤 언어로 변환하는 것인지 명시해 줘야 한다.

Code generator 정의 코드

Blockly.Python['short_math']=function(block)
{
    var num_1=Blockly.Python.valueToCode(block,'a',Blockly.Python.ORDER_ATOMIC);
    var dropdown_select = block.getFieldValue('select');
    var num_2=Blockly.Python.valueToCode(block,'b',Blockly.Python.ORDER_ATOMIC);
    var code=num_1+dropdown_select+"= "+num_2+"\n";
    return code;
}

전체코드

Code generation 시작 함수

Blockly.Python['short_math']=function(block){
    //함수 내용
}

"Short_math" 블록에 Python으로 된 코드를 붙일 것이다. 라는 뜻
여기서 short_math는 이미 정의해 놓은 Short_math 블록의 type을 가리킨다.
구조 자체는 Javascript 스타일로 블록을 정의할 때와 매우 유사하지만, 시작 부분이 Blockly.Blocks가 아니라 Blockly.Python 이라는 것이다.
이 Python 부분에서 블록을 어떤 언어로 변환한 것인지를 지정한다.
Python 이외의 다른 언어로의 변환을 하고 싶다면, 시작 부분을 다음과 같이 써주면 된다.

Blockly.Lua // Lua로 변환
Blockly.Javascript // Javascript로 변환
Blockly.Dart // Dart로 변환
Blockly.PHP // PHP로 변환

이 후 function(block) 부분에서 block을 파라미터로 받는 함수를 선언하고, 그 함수 안에 code generation의 단계가 기술된다.

근데 내가 현재 진행중인 project에서는 언어로의 변환을 smartthings에 대한 언어로 바꿔야 하기 때문에 Blockly.SmartThings를 사용해 주었다.

Block으로부터 값 읽어오기

Short_math 블록의 경우 읽어 와야 할 값이 총 3가지가 있다.
1. 첫 번째 value input(첫 번째 피연산자)의 값
2. Dropdown field 에서 선택된 option의 값(연산의 종류)
3. 두 번째 value input(두 번째 피연산자)의 값

var num_1=Blockly.Python.valueToCode(block,'a',Blockly.Python.ORDER_ATOMIC);
var dropdown_select = block.getFieldValue('select');
var num_2=Blockly.Python.valueToCode(block,'b',Blockly.Python.ORDER_ATOMIC);

첫 번째 줄 : 첫 번째 value input을 읽어오는 코드
Blockly.Python.valueToCode() 함수를 사용해 값을 읽어오고 있다. (python 이외의 다른 언어를 사용한다면, python 부분을 다른 언어로 바꿔주면 된다.)
Blockly에서 블록에 할당된 input이나 field의 값을 읽어 올때는 이처럼 미리 지정된 함수를 사용하는 데, 값 읽기에 사용되는 함수 몇가지는 다음과 같다.

valueToCode() : Value input의 값을 읽는다. parameter는 3개이며, 순서대로
1. 해당 input이 위치하는 블록
2. input의 이름
3. 그 값이 속하는 타입의 연산자 우선순위

statementToCode() : Statement input의 값을 읽습니다. 파라미터는 2개이며, 순서대로 해당 input이 위치하는 블록과 input의 이름을 의미한다.
block.getFieldValue() : 블록에 단일 Field가 있는 경우, 이 field의 값을 읽어오는 함수.
특이하게도 이 함수는 위의 두 함수와 다르게 독립적으로 호출되지 않고 block의 하위 메소드로 호출된다. 그래서 어떤 블록의 field를 읽어 오는 것인지 명시해 줄 필요가 없고, 읽어 오려는 field의 이름만 parameter로 넘겨주면 된다.

연산자 우선순위

valueToCode() 함수는 세 번째 파라미터로 연산자 우선순위를 받는다.
연산자 우선순위 : 코드를 생성하고 조합하는 과정에서 꽤 중요한 역할을 한다.
Blockly/generators/(언어명).js 파일 안에 각 언어별 연산자 우선순위가 지정되어 있다.
미리 지정되어 있는 값이기 때문에 사용할 때는 그 값을 고민할 필요 없이, 미리 지정되어 있는 해당 연산자의 이름만 호출해서 사용 가능하다.

이상한 점 : valueToCode()가 가져오는 건 보통 Number나 String 등의 값일텐데, 이런 단일 값을 가져올 때도 우선순위를 명시해 주어야 하는가 하는 점이다.
결론 : 예 이다.
연산자 우선순위는 나중에 코드를 생성하고 조합하는 과정에서 필요한 부분을 괄호로 묶어 주는 등 작업을 하는 데 필요하다.
지금 다루고 있는 블록에 어떤 input이 결합하느냐에 따라 최종 코드가 어떤 모양이 될 지 결정되고, 어떤 부분이 괄호로 묶일지 하는 점도 달라진다.
이런 점을 사람이 하나하나 하드코딩해 줄 수 없기 때문에, Blockly는 연산자 우선순위를 이용해 코드의 형태를 결정하는 방식을 취하고 있다.
그렇게 하기 위해서는 숫자하나, 문자열 하나에도 모두 우선순위를 부여해서 Blockly가 다루는 모든 타입의 결합형태를 파악할 필요가 있다.

자주 쓰이는 연산자 우선순위
ORDER_ATOMIC : 숫자나 String 등 하나의 독립적 값을 의미
ORDER_COLLECTION : 리스트나 튜플 등 여러 개의 요소가 하나의 큰 덩어리를 이루는 형태의 자료구조
ORDER_NONE : 정의되어 있는 어떤 연산자에도 해당되지 않는 것을 리턴하는 경우나 리턴값이 없는 경우 등에 사용

코드 생성

이 부분이 실제 Python code를 생성해서 String으로 리턴해주는 부분이다.

var code=num_1+dropdown_select+"= "+num_2+"\n";
return code;

첫 번째 줄을 리턴한 code를 생성하는 부분이다.
앞에서 읽어 온 다양한 값들과 파이썬 구문의 일부를 조합하여 하나의 완전한 코드를 만들어내고 있다.

실제로 내가 하는 프로젝트의 코드를 일부 가져온다면

var c = new Calculation()
  c.inputs = inputList
  c.operator = dropdown_op 
  c.left = left   // =  left+" "+dropdown_op+" "+right;
  c.right = right  
  // TODO: Change ORDER_NONE to the correct strength.
  return c;

위에서의 code가 여기서의 c를 의미한다고 생각하면 될 것 같다.

주의해야 할 점
output이 있는 블록과 없는 블록은 code를 리턴하는 방식이 서로 다르다는 것이다.
short_math는 output이 없는 블록이므로 단순히 code 한 줄만 리턴하지만 (output이 없는 블록은 코드만 리턴해야 한다.)
output이 있는 블록의 경우 실행결과 리턴되는 값이 있다는 뜻이기 때문에, 그 리턴 값에 대한 우선순위를 같이 명시해 줘야 한다.

return [code, Blockly.Python.ORDER_ATOMIC];

만약 리턴하는 값이 이미 정의된 연산자 범주 안에 속하지 않거나, 블록 모양상으로만 output이 존재하고 실제로는 리턴값이 없는 경우라면, ORDER_ATOMIC 대신 ORDER_NONE을 리턴할 수 있다.

0개의 댓글