@@ -47,13 +47,17 @@ class RubyToBlocksConverter {
4747
4848 reset ( ) {
4949 this . _context = {
50- blocks : { } ,
51- blockTypes : { } ,
5250 currentNode : null ,
5351 errors : [ ] ,
52+ argumentBlocks : { } ,
53+ procedureCallBlocks : { } ,
54+
55+ blocks : { } ,
56+ blockTypes : { } ,
5457 localVariables : { } ,
5558 variables : { } ,
56- lists : { }
59+ lists : { } ,
60+ procedures : { }
5761 } ;
5862 if ( this . vm && this . vm . runtime && this . vm . runtime . getTargetForStage ) {
5963 this . _loadVariables ( this . vm . runtime . getTargetForStage ( ) ) ;
@@ -138,12 +142,21 @@ class RubyToBlocksConverter {
138142 }
139143
140144 _saveContext ( ) {
141- return {
142- blocks : Object . assign ( { } , this . _context . blocks ) ,
143- localVariables : Object . assign ( { } , this . _context . localVariables ) ,
144- variables : Object . assign ( { } , this . _context . variables ) ,
145- lists : Object . assign ( { } , this . _context . lists )
146- } ;
145+ const includes = [
146+ 'blocks' ,
147+ 'blockTypes' ,
148+ 'localVariables' ,
149+ 'variables' ,
150+ 'lists' ,
151+ 'procedures'
152+ ] ;
153+
154+ const saved = { } ;
155+ Object . keys ( this . _context ) . filter ( k => includes . indexOf ( k ) >= 0 )
156+ . forEach ( k => {
157+ saved [ k ] = Object . assign ( { } , this . _context [ k ] ) ;
158+ } ) ;
159+ return saved ;
147160 }
148161
149162 // could not restore attributes.
@@ -379,6 +392,25 @@ class RubyToBlocksConverter {
379392 return this . _findOrCreateVariableOrList ( name , Variable . LIST_TYPE ) ;
380393 }
381394
395+ _findProcedure ( name ) {
396+ name = name . toString ( ) ;
397+ return this . _context . procedures [ name ] ;
398+ }
399+
400+ _createProcedure ( name ) {
401+ name = name . toString ( ) ;
402+ let procedure = this . _context . procedures [ name ] ;
403+ if ( procedure ) {
404+ throw new RubyToBlocksConverterError (
405+ this . _context . currentNode ,
406+ `already defined My Block "${ name } ".`
407+ ) ;
408+ }
409+ procedure = { } ;
410+ this . _context . procedures [ name ] = procedure ;
411+ return procedure ;
412+ }
413+
382414 _getSource ( node ) {
383415 const expression = node . $loc ( ) . $expression ( ) ;
384416 if ( expression === Opal . nil ) {
@@ -458,14 +490,13 @@ class RubyToBlocksConverter {
458490 this . _setBlockType ( block , 'value_boolean' ) ;
459491 variable . isBoolean = true ;
460492
461- Object . keys ( this . _context . blocks )
462- . filter ( id => this . _context . blocks [ id ] . opcode === 'argument_reporter_string_number' )
463- . forEach ( id => {
493+ if ( this . _context . argumentBlocks . hasOwnProperty ( variable . id ) ) {
494+ this . _context . argumentBlocks [ variable . id ] . forEach ( id => {
464495 const b = this . _context . blocks [ id ] ;
465- if ( b . fields . VALUE . variableId === variable . id ) {
466- b . opcode = 'argument_reporter_boolean' ;
467- }
496+ b . opcode = 'argument_reporter_boolean' ;
497+ this . _setBlockType ( b , 'value_boolean' ) ;
468498 } ) ;
499+ }
469500
470501 return true ;
471502 }
@@ -795,6 +826,42 @@ class RubyToBlocksConverter {
795826 block = this . _createRubyStatementBlock ( 'wait' ) ;
796827 }
797828 break ;
829+ default :
830+ if ( this . _findProcedure ( name ) ) {
831+ const procedure = this . _findProcedure ( name ) ;
832+ if ( procedure . argTypes . length === args . length ) {
833+ block = this . _createBlock ( 'procedures_call' , 'statement' , {
834+ mutation : {
835+ tagName : 'mutation' ,
836+ children : [ ] ,
837+ proccode : procedure . procCode ,
838+ warp : 'false'
839+ }
840+ } ) ;
841+
842+ procedure . argTypes . forEach ( ( argType , i ) => {
843+ const arg = args [ i ] ;
844+ const argumentId = procedure . argumentIds [ i ] ;
845+ if ( argType === 'boolean' ) {
846+ if ( this . _isFalseOrBooleanBlock ( arg ) ) {
847+ if ( arg !== false ) {
848+ this . _addInput ( block , argumentId , arg , null ) ;
849+ }
850+ return ;
851+ }
852+ } else if ( this . _isNumberOrBlock ( arg ) || this . _isStringOrBlock ( arg ) ) {
853+ this . _addTextInput ( block , argumentId , _ . isNumber ( arg ) ? arg . toString ( ) : arg , '' ) ;
854+ return ;
855+ }
856+ throw new RubyToBlocksConverterError (
857+ this . _context . currentNode ,
858+ `invalid type of My Block "${ name } " argument #${ i + 1 } `
859+ ) ;
860+ } ) ;
861+ block . mutation . argumentids = JSON . stringify ( procedure . argumentIds ) ;
862+ }
863+ }
864+ break ;
798865 }
799866 } else if ( this . _isVariableBlock ( receiver ) ) {
800867 switch ( name ) {
@@ -1286,15 +1353,20 @@ class RubyToBlocksConverter {
12861353 opcode = 'argument_reporter_string_number' ;
12871354 blockType = 'value' ;
12881355 }
1289- return this . _createBlock ( opcode , blockType , {
1356+ const block = this . _createBlock ( opcode , blockType , {
12901357 fields : {
12911358 VALUE : {
12921359 name : 'VALUE' ,
1293- value : variable . name ,
1294- variableId : variable . id
1360+ value : variable . name
12951361 }
12961362 }
12971363 } ) ;
1364+ if ( this . _context . argumentBlocks . hasOwnProperty ( variable . id ) ) {
1365+ this . _context . argumentBlocks [ variable . id ] . push ( block . id ) ;
1366+ } else {
1367+ this . _context . argumentBlocks [ variable . id ] = [ block . id ] ;
1368+ }
1369+ return block ;
12981370 }
12991371
13001372 return varName ;
@@ -1364,6 +1436,97 @@ class RubyToBlocksConverter {
13641436 _onGvasgn ( node ) {
13651437 return this . _onIvasgn ( node ) ;
13661438 }
1439+
1440+ _onDefs ( node ) {
1441+ this . _checkNumChildren ( node , 4 ) ;
1442+
1443+ const saved = this . _saveContext ( ) ;
1444+
1445+ const receiver = this . _process ( node . children [ 0 ] ) ;
1446+ if ( receiver === Self ) {
1447+ const procedureName = node . children [ 1 ] . toString ( ) ;
1448+ const block = this . _createBlock ( 'procedures_definition' , 'hat' , {
1449+ topLevel : true
1450+ } ) ;
1451+ const procedure = this . _createProcedure ( procedureName ) ;
1452+
1453+ const customBlock = this . _createBlock ( 'procedures_prototype' , 'statement' , {
1454+ shadow : true
1455+ } ) ;
1456+ this . _addInput ( block , 'custom_block' , customBlock ) ;
1457+
1458+ const args = this . _process ( node . children [ 2 ] ) ;
1459+
1460+ // define as local variable for guessing argument type is bool or not.
1461+ args . forEach ( argName => {
1462+ this . _findOrCreateVariable ( argName ) ;
1463+ } ) ;
1464+ let body = this . _process ( node . children [ 3 ] ) ;
1465+ if ( ! _ . isArray ( body ) ) {
1466+ body = [ body ] ;
1467+ }
1468+ if ( this . _isBlock ( body [ 0 ] ) ) {
1469+ block . next = body [ 0 ] . id ;
1470+ body [ 0 ] . parent = block . id ;
1471+ }
1472+
1473+ const procCode = [ procedureName ] ;
1474+ const argDefaults = [ ] ;
1475+ const argTypes = [ ] ;
1476+ const argBlocks = args . map ( argName => {
1477+ const variable = this . _findOrCreateVariable ( argName ) ;
1478+ let opcode ;
1479+ if ( variable . isBoolean ) {
1480+ opcode = 'argument_reporter_boolean' ;
1481+ procCode . push ( '%b' ) ;
1482+ argDefaults . push ( 'false' ) ;
1483+ argTypes . push ( 'boolean' ) ;
1484+ } else {
1485+ opcode = 'argument_reporter_string_number' ;
1486+ procCode . push ( '%s' ) ;
1487+ argDefaults . push ( '' ) ;
1488+ argTypes . push ( 'string_number' ) ;
1489+ }
1490+ return this . _createBlock ( opcode , 'value' , {
1491+ fields : {
1492+ VALUE : {
1493+ name : 'VALUE' ,
1494+ value : argName
1495+ }
1496+ } ,
1497+ shadow : true
1498+ } ) ;
1499+ } ) ;
1500+
1501+ procedure . procCode = procCode . join ( ' ' ) ;
1502+ procedure . argDefaults = argDefaults ;
1503+ procedure . argNames = args ;
1504+ procedure . argTypes = argTypes ;
1505+ procedure . argumentIds = argBlocks . map ( argBlock => {
1506+ const id = Blockly . utils . genUid ( ) ;
1507+ this . _addInput ( customBlock , id , argBlock ) ;
1508+ return id ;
1509+ } ) ;
1510+
1511+ customBlock . mutation = {
1512+ argumentdefaults : JSON . stringify ( procedure . argDefaults ) ,
1513+ argumentids : JSON . stringify ( procedure . argumentIds ) ,
1514+ argumentnames : JSON . stringify ( procedure . argNames ) ,
1515+ children : [ ] ,
1516+ proccode : procedure . procCode ,
1517+ tagName : 'mutation' ,
1518+ warp : 'false'
1519+ } ;
1520+
1521+ this . _restoreContext ( { localVariables : saved . localVariables } ) ;
1522+
1523+ return block ;
1524+ }
1525+
1526+ this . _restoreContext ( saved ) ;
1527+
1528+ return this . _createRubyStatementBlock ( this . _getSource ( node ) ) ;
1529+ }
13671530}
13681531
13691532const targetCodeToBlocks = function ( vm , target , code , errors = [ ] ) {
0 commit comments