<reponame>iahuang/scratch-gcc
import os
import json
from . import SPArgumentName, SPArithmetic, SPAssign, SPConstant, SPFunctionDefinition, SPModule, SPNode, SPVariableName
from .. import scratch

class CompilationContext:
    def __init__(self):
        self.enclosingFunction: SPFunctionDefinition = None
        self.functionCalls = []
        self.supportingBlocks = []

class SPModuleCompiler:
    def __init__(self, templateFile=None, templateTarget=None):
        """
        The SPModule Compiler works by loading an existing sb3 file to use as a template,
        building the necessary code blocks, and loading them into an existing sprite in that template file.

        templateFile specifies the file to use, if none is specified, then it defaults to using the built-in template

        templateTarget specifies the sprite to load the program blocks into
        """
        if templateFile == None:
            thisPath = os.path.dirname(os.path.realpath(__file__))
            templateFile = thisPath+"/resources/scratchpy_compiler_template.sb3"
            templateTarget = "__main__"
        
        self.templateFile = templateFile
        self.templateTarget = templateTarget
        self.module: SPModule = None

        # The working environment - an instance of a scratch project loaded from the template
        self.scratchProj = scratch.ScratchProject(self.templateFile)

        self.context = CompilationContext()

        self.scratchProj.meta["description"] = "File auto-generated by scratch-gcc https://github.com/iahuang/scratch-gcc"
    
    def compileModule(self, module):
        self.module = module

        # Start by creating an scratch project instance to work with

        codeTarget = self.scratchProj.getTarget(self.templateTarget)
        stage = self.scratchProj.getStage()

        # add variables to stage
        
        for variable in self.module.getVariables():
            codeTarget.addVariable(variable.name, variable.value)
        
        # create blocks for functions

        for function in self.module.functionBlocks:
            # Create function def block at [0, 0]
            defBlock = codeTarget.createBlock([0, 0])
            defBlock.opcode = "procedures_definition"
            
            # Add prototype block
            protoBlock = codeTarget.createBlock(parent=defBlock)
            protoBlock.opcode = "procedures_prototype"
            protoBlock.shadow = True
            
            # Link prototype block to function def block
            defBlock.inputs.append(scratch.BlockInput("custom_block", 1, protoBlock.id))
            
            # Create argument ids
            argIds = []
            argNames = []
            argDefaults = []

            for arg in function.args:
                argIds.append(arg.id)
                argNames.append(arg.name)
                argDefaults.append(arg.getArgumentDefault())
            
            proccodeArgs = ""

            # Proccode: "[name] [arg1_type] [arg2_type] etc."
            for arg in function.args:
                proccodeArgs+=" "+("%b" if arg.isBoolean else "%s")

            # Set prototype block data
            protoBlock.mutation = {
                "tagName": "mutation",
                "children": [],
                "proccode": f"{function.fname}"+proccodeArgs,
                "argumentids": json.dumps(argIds),
                "argumentnames": json.dumps(argNames),
                "argumentdefaults": json.dumps(argDefaults),
                "warp": "false"
            }

            # Add argument reporters

            reporters = []
            for arg in function.args:
                reporter = codeTarget.createBlock(parent=protoBlock)
                reporter.opcode = "argument_reporter_string_number"

                reporter.fields.append(scratch.BlockField("VALUE", [arg.name, None]))

                protoBlock.inputs.append(scratch.BlockInput(arg.id, 1, reporter.id))

            # compile the body of the function
            prevNode = defBlock
            for node in function.body:
                blocks = self.spNodeToScratchBlocks(codeTarget, node)
                for block in blocks:
                    prevNode.nextId = block.id
                    block.parentId = prevNode.id
                    prevNode = block
    
    def _spNodeToBlockInput(self, name, target: scratch.ScratchTarget, node: SPNode)->scratch.Block:
        if type(node) == SPConstant:
            return scratch.BlockInput(name, 1, [4, node.value])
        if type(node) == SPArgumentName:
            reporter = target.createBlock()
            reporter.opcode = "argument_reporter_string_number"
            reporter.fields.append(scratch.BlockField("VALUE", [node.argname, None]))
            return scratch.BlockInput(name, 1, reporter.id)
        if type(node) == SPVariableName:
            var = target.findVariableByName(node.variableName)
            input = scratch.BlockInput(name, 1, var.name, third=var.id)
            input.isVariable = True
            return input
        if type(node) == SPArithmetic:
            block = self.spNodeToReporterBlock(target, node)
            return scratch.BlockInput(name, 1, block.id, valueTypeId=1)
        print(f'Compilation warning: unsupported node type "{type(node).__name__}" for block input')
    
    def spNodeToReporterBlock(self, target: scratch.ScratchTarget, node: SPNode):
        block = target.createBlock()

        if type(node) == SPArithmetic:
            opcode = {
                "add": "operator_add",
                "sub": "operator_subtract",
                "mul": "operator_multiply",
                "div": "operator_divide"
            }[node.op]
            block.opcode = opcode

            leftInput = self._spNodeToBlockInput("NUM1", target, node=node.left)

            rightInput = self._spNodeToBlockInput("NUM2", target, node=node.right)

            leftInput.parentId = block
            rightInput.parentId = block

            block.inputs.append(leftInput)
            block.inputs.append(rightInput)

        else:
            print(f'Compilation warning: unsupported node type "{type(node).__name__}" for reporter')

        return block

    def spNodeToScratchBlocks(self, target: scratch.ScratchTarget, node: SPNode):
        blocks = []
        if type(node) == SPAssign:
            for assignTarget in node.targets:
                block = target.createBlock()
                block.opcode = "data_setvariableto"
                block.fields.append(scratch.BlockField("VARIABLE", [
                    assignTarget.variableName,
                    target.findVariableByName(assignTarget.variableName).id
                ]))

                assignValueBlock = self.spNodeToReporterBlock(target, node.value)

                block.inputs.append(scratch.BlockInput("VALUE", 1, assignValueBlock.id))
                blocks.append(block)
        else:
            print(f'Compilation warning: unsupported node type "{type(node).__name__}"')
        
        return blocks

    def exportSB3(self, filename):
        self.scratchProj.saveToFile(filename)
    
    def exportProjectJSON(self, filename):
        with open(filename, 'w') as fl:
            json.dump(self.scratchProj.serialize(), fl)
    
    def __enter__(self):
        return self
    
    def __exit__(self, *args):
        self.scratchProj.__exit__()
import os
import json
from . import SPArgumentName, SPArithmetic, SPAssign, SPConstant, SPFunctionDefinition, SPModule, SPNode, SPVariableName
from .. import scratch

class CompilationContext:
    def __init__(self):
        self.enclosingFunction: SPFunctionDefinition = None
        self.functionCalls = []
        self.supportingBlocks = []

class SPModuleCompiler:
    def __init__(self, templateFile=None, templateTarget=None):
        """
        The SPModule Compiler works by loading an existing sb3 file to use as a template,
        building the necessary code blocks, and loading them into an existing sprite in that template file.

        templateFile specifies the file to use, if none is specified, then it defaults to using the built-in template

        templateTarget specifies the sprite to load the program blocks into
        """
        if templateFile == None:
            thisPath = os.path.dirname(os.path.realpath(__file__))
            templateFile = thisPath+"/resources/scratchpy_compiler_template.sb3"
            templateTarget = "__main__"
        
        self.templateFile = templateFile
        self.templateTarget = templateTarget
        self.module: SPModule = None

        # The working environment - an instance of a scratch project loaded from the template
        self.scratchProj = scratch.ScratchProject(self.templateFile)

        self.context = CompilationContext()

        self.scratchProj.meta["description"] = "File auto-generated by scratch-gcc https://github.com/iahuang/scratch-gcc"
    
    def compileModule(self, module):
        self.module = module

        # Start by creating an scratch project instance to work with

        codeTarget = self.scratchProj.getTarget(self.templateTarget)
        stage = self.scratchProj.getStage()

        # add variables to stage
        
        for variable in self.module.getVariables():
            codeTarget.addVariable(variable.name, variable.value)
        
        # create blocks for functions

        for function in self.module.functionBlocks:
            # Create function def block at [0, 0]
            defBlock = codeTarget.createBlock([0, 0])
            defBlock.opcode = "procedures_definition"
            
            # Add prototype block
            protoBlock = codeTarget.createBlock(parent=defBlock)
            protoBlock.opcode = "procedures_prototype"
            protoBlock.shadow = True
            
            # Link prototype block to function def block
            defBlock.inputs.append(scratch.BlockInput("custom_block", 1, protoBlock.id))
            
            # Create argument ids
            argIds = []
            argNames = []
            argDefaults = []

            for arg in function.args:
                argIds.append(arg.id)
                argNames.append(arg.name)
                argDefaults.append(arg.getArgumentDefault())
            
            proccodeArgs = ""

            # Proccode: "[name] [arg1_type] [arg2_type] etc."
            for arg in function.args:
                proccodeArgs+=" "+("%b" if arg.isBoolean else "%s")

            # Set prototype block data
            protoBlock.mutation = {
                "tagName": "mutation",
                "children": [],
                "proccode": f"{function.fname}"+proccodeArgs,
                "argumentids": json.dumps(argIds),
                "argumentnames": json.dumps(argNames),
                "argumentdefaults": json.dumps(argDefaults),
                "warp": "false"
            }

            # Add argument reporters

            reporters = []
            for arg in function.args:
                reporter = codeTarget.createBlock(parent=protoBlock)
                reporter.opcode = "argument_reporter_string_number"

                reporter.fields.append(scratch.BlockField("VALUE", [arg.name, None]))

                protoBlock.inputs.append(scratch.BlockInput(arg.id, 1, reporter.id))

            # compile the body of the function
            prevNode = defBlock
            for node in function.body:
                blocks = self.spNodeToScratchBlocks(codeTarget, node)
                for block in blocks:
                    prevNode.nextId = block.id
                    block.parentId = prevNode.id
                    prevNode = block
    
    def _spNodeToBlockInput(self, name, target: scratch.ScratchTarget, node: SPNode)->scratch.Block:
        if type(node) == SPConstant:
            return scratch.BlockInput(name, 1, [4, node.value])
        if type(node) == SPArgumentName:
            reporter = target.createBlock()
            reporter.opcode = "argument_reporter_string_number"
            reporter.fields.append(scratch.BlockField("VALUE", [node.argname, None]))
            return scratch.BlockInput(name, 1, reporter.id)
        if type(node) == SPVariableName:
            var = target.findVariableByName(node.variableName)
            input = scratch.BlockInput(name, 1, var.name, third=var.id)
            input.isVariable = True
            return input
        if type(node) == SPArithmetic:
            block = self.spNodeToReporterBlock(target, node)
            return scratch.BlockInput(name, 1, block.id, valueTypeId=1)
        print(f'Compilation warning: unsupported node type "{type(node).__name__}" for block input')
    
    def spNodeToReporterBlock(self, target: scratch.ScratchTarget, node: SPNode):
        block = target.createBlock()

        if type(node) == SPArithmetic:
            opcode = {
                "add": "operator_add",
                "sub": "operator_subtract",
                "mul": "operator_multiply",
                "div": "operator_divide"
            }[node.op]
            block.opcode = opcode

            leftInput = self._spNodeToBlockInput("NUM1", target, node=node.left)

            rightInput = self._spNodeToBlockInput("NUM2", target, node=node.right)

            leftInput.parentId = block
            rightInput.parentId = block

            block.inputs.append(leftInput)
            block.inputs.append(rightInput)

        else:
            print(f'Compilation warning: unsupported node type "{type(node).__name__}" for reporter')

        return block

    def spNodeToScratchBlocks(self, target: scratch.ScratchTarget, node: SPNode):
        blocks = []
        if type(node) == SPAssign:
            for assignTarget in node.targets:
                block = target.createBlock()
                block.opcode = "data_setvariableto"
                block.fields.append(scratch.BlockField("VARIABLE", [
                    assignTarget.variableName,
                    target.findVariableByName(assignTarget.variableName).id
                ]))

                assignValueBlock = self.spNodeToReporterBlock(target, node.value)

                block.inputs.append(scratch.BlockInput("VALUE", 1, assignValueBlock.id))
                blocks.append(block)
        else:
            print(f'Compilation warning: unsupported node type "{type(node).__name__}"')
        
        return blocks

    def exportSB3(self, filename):
        self.scratchProj.saveToFile(filename)
    
    def exportProjectJSON(self, filename):
        with open(filename, 'w') as fl:
            json.dump(self.scratchProj.serialize(), fl)
    
    def __enter__(self):
        return self
    
    def __exit__(self, *args):
        self.scratchProj.__exit__()
The SPModule Compiler works by loading an existing sb3 file to use as a template,         building the necessary code blocks, and loading them into an existing sprite in that template file.          templateFile specifies the file to use, if none is specified, then it defaults to using the built-in template          templateTarget specifies the sprite to load the program blocks into # The working environment - an instance of a scratch project loaded from the template # Start by creating an scratch project instance to work with # add variables to stage # create blocks for functions # Create function def block at [0, 0] # Add prototype block # Link prototype block to function def block # Create argument ids # Proccode: "[name] [arg1_type] [arg2_type] etc." # Set prototype block data # Add argument reporters # compile the body of the function