Code Generators

Most applications of Blockly require the user's program to be translated into JavaScript, Python, Lua, Dart, PHP, or some other language. Blockly for iOS includes a service for generating such code, but actually leverages Blockly for Web's code generators to perform this task.

The iOS service (CodeGeneratorService) forwards each code generation request to a WKWebView instance running the web version of Blockly, which asynchronously executes the request. The web instance then returns generated code (or an error) back to iOS, and an appropriate callback on the original request is called to signify completion.

Here's a diagram outlining this flow:

Code generator flow for iOS

Create Code Generator Service

When creating a CodeGeneratorService, you will first need to copy two files from the Blockly for Web repo into your project:

  • blockly_compressed.js: This is the compressed version of the Blockly engine.
  • msg/js/<your app language>.js: This contains translations for all internationalized messages in Blockly. Pick the language appropriate for your app and copy it into your project.

Now you can create a CodeGeneratorService. You only need to create one, which should be stored as an instance variable.

Swift

var codeGeneratorService: CodeGeneratorService = {
  let codeGeneratorService = CodeGeneratorService(
    jsCoreDependencies: [
      // The JS file containing the Blockly engine
      "blockly_compressed.js",
      // The JS file containing a list of internationalized messages
      // (copied from `/blockly/msg/js/en.js`)
      "blockly_messages_en.js"
    ])
  return codeGeneratorService
}()

Objective-C

@interface MyObject: NSObject {
  BKYCodeGeneratorService *_codeGeneratorService;
}
@end

@implementation MyObject

- (id)init {
  self = [super init];
  if (self) {
    _codeGeneratorService =
      [[BKYCodeGeneratorService alloc] initWithJsCoreDependencies: @[
        // The JS file containing the Blockly engine
        @"blockly_compressed.js",
        // The JS file containing a list of internationalized messages
        // (copied from `/blockly/msg/js/en.js`)
        @"blockly_messages_en.js"]];
  }
  return self;
}

@end

Set Request Builder

In order to generate code for a given Workspace, you will need to create a CodeGeneratorServiceRequest and pass it into your CodeGeneratorService.

Since most requests will use the same blocks and generators, they are created from a builder that makes it easy to generate new requests from the current workspace. First, create a CodeGeneratorServiceRequestBuilder that can be re-used for each request and specify the following information:

  • jsBlockGeneratorFiles: A list of files defining generator methods for the language you wish to target. You will need to specify at least one (but typically both) of the following here:
    1. Generator file for all the default blocks in Blockly: Blockly for Web provides many of these default generators for different languages. They can be found in its repo's root directory under the name "<language>_compressed.js".
      e.g. To use the default Lua generator methods, you would copy lua_compressed.js into your project and specify that file here.
    2. Generator file(s) for any custom blocks you've defined: You will need to specify generators for any custom blocks that are defined in your workspace or else code generation will fail. Click here for more information on how to define your own generators.
  • jsGeneratorObject: The JavaScript object containing all the generator methods for your blocks, as defined inside jsBlockGeneratorFiles.
    e.g. For the Lua generator object, you would specify "Blockly.Lua" here.
  • jsonBlockDefinitionFiles: A list of files containing JSON definitions for all default and custom blocks used in your workspace.

The CodeGeneratorServiceRequestBuilder must be assigned to the CodeGeneratorService before code generation can be requested.

Here's an example of how to create and assign a request builder for JavaScript code generation:

Swift

var codeGeneratorService: CodeGeneratorService = {
  // Create service
  let codeGeneratorService = ...

  // Create builder for creating code generator service requests
  let requestBuilder = CodeGeneratorServiceRequestBuilder(
    // The name of the object containing all of the JavaScript generators
    jsGeneratorObject: "Blockly.JavaScript"
  )
  requestBuilder.addJSBlockGeneratorFiles([
    // File with JavaScript generators for all the default blocks in Blockly
    "javascript_compressed.js",
    // File with JavaScript generators for the blocks defined in `custom_blocks.json`
    "custom_javascript_generators.js"
  ])
  // Definitions for all default blocks in Blockly
  requestBuilder.addJSONBlockDefinitionFiles(fromDefaultFiles: .allDefault)
  // Definitions for custom blocks that are being used inside the workspace
  requestBuilder.addJSONBlockDefinitionFiles(["custom_blocks.json"])

  // Set the request builder for the CodeGeneratorService.
  codeGeneratorService.setRequestBuilder(requestBuilder, shouldCache: true)

  return codeGeneratorService
}

Objective-C

@interface MyObject: NSObject {
  BKYCodeGeneratorService *_codeGeneratorService;
}
@end

@implementation MyObject

- (id)init {
  self = [super init];
  if (self) {
    // Create service
    _codeGeneratorService = ...

    // Create builder for creating code generator service requests
    BKYCodeGeneratorServiceRequestBuilder *requestBuilder =
      [[BKYCodeGeneratorServiceRequestBuilder alloc] initWithJSGeneratorObject:@"Blockly.JavaScript"];
    [requestBuilder addJSBlockGeneratorFiles:@[
      // File with JavaScript generators for all the default blocks in Blockly
      @"javascript_compressed.js",
      // File with JavaScript generators for the blocks defined in `custom_blocks.json`
      @"custom_javascript_generators.js"
    ]];
    // Definitions for all default blocks in Blockly
    [requestBuilder addJSONBlockDefinitionFilesFromDefaultFiles:BKYBlockJSONFileAllDefault];
    // Definitions for custom blocks that are being used inside the workspace
    [requestBuilder addJSONBlockDefinitionFiles:@[@"custom_blocks.json"]];

    // Set the request builder for the CodeGeneratorService.
    [_codeGeneratorService setRequestBuilder:requestBuilder shouldCache:YES];
  }
  return self;
}

@end

Generate Code

Now, you can finally request code generation. For each request that is made, you'll need to specify the following:

  • onCompletion: A closure that is called when the code generation is successful.
  • onError: A closure that is called when the code generation fails.

Here's an example of how to generate JavaScript code from a given Workspace:

Swift

// The UUID of the current code generation request.
var _currentRequestUUID: String = ""

func generateJavaScriptCode(forWorkbench workbench: WorkbenchViewController) {
  guard let workspace = workbench.workspace else {
    print("Workbench does not contain a workspace.")
    return
  }

  do {
    _currentRequestUUID = try codeGeneratorService.generateCode(
      forWorkspace: workspace,
      onCompletion: { requestUUID, code in
        print("JavaScript code generation was successful:\n\(code)")
      },
      onError: { requestUUID, error in
        print("JavaScript code generation failed:\n\(error)")
      })
  } catch let error {
    print("Could not create code request:\n\(error)")
  }
}

Objective-C

// The UUID of the current code generation request.
@property(nonatomic) NSString *currentRequestUUID;

- (void)generateJavaScriptCodeForWorkbench:(BKYWorkbenchViewController*)workbench {
  if (!workbench.workspace) {
    NSLog(@"Workbench does not contain a workspace.");
    return;
  }

  NSError *error = nil;

  _currentRequestUUID =
    [_codeGeneratorService generateCodeForWorkspace:workbench.workspace
      error:&error
      onCompletion:^(NSString *requestUUID, NSString *code) {
        NSLog(@"JavaScript code generation was successful:\n%@", code);
      }
      onError:^(NSString *requestUUID, NSString *error) {
        NSLog(@"JavaScript code generation failed:\n%@", error);
      }];

  if (error) {
    NSLog(@"Could not create code request:\n%@", error);
    return;
  }
}

The CodeGeneratorService is thread-safe, and executes each request asynchronously and in the order it was queued. However, it only processes one request at a time due to the memory overhead of using multiple WKWebView instances concurrently.

Cancel Existing Requests

In practice, all pending requests for a CodeGeneratorService should be cancelled when its owning object is deallocated. In this case, you should call:

Swift

deinit {
  codeGeneratorService.cancelAllRequests()
}

Objective-C

- (void)dealloc {
  [_codeGeneratorService cancelAllRequests];
}

To cancel an individual request, you can call:

Swift

codeGeneratorService.cancelRequest(_currentRequestUUID)

Objective-C

[_codeGeneratorService cancelRequest:_currentRequestUUID];