Calico: Language Development

From IPRE Wiki
Jump to: navigation, search

A Language file in Calico/languages/*/Calico*.[dll|py] defines everything needed for a language: the editing document, the language engine, etc. Documents can do things like open, save, and display data for editing. Engines can do things like execute text, execute files, and parse files. Engines also allow for the languages to share data and functionality.

NOTE: there is now a sample program in Calico which will automatically create a Python language folder (complete with example). Simply run the program from Calico (menu -> File -> Examples -> Python -> CS -> MakeACalicoLanguage.py). Or, you can follow the step-by-step directions below.

Manual Language Development

You can define a Calico Language in terms of another Calico Language (currently just Python and Ruby). For this example, you need to create a directory in Calico/languages, and create a file in that directory.

For example:

  1. create a folder named Calico/languages/Test (name can be most anything)
  2. create a file in that folder named CalicoTest.py (name should start with "Calico")

In our Test language we need to make some decisions:

  • What kind of language will it be: GUI or text?
  • What will the file extension be?

In this example, Test will be a text-based language, and files that have the extension ".tt" will be associated with this language inside Calico. (You may also associate the extension with Calico in your operating system so that Calico will open when you double-click a .tt file).

Your language definition program could have something like the following:

# File: Calico/languages/CalicoTest.py
# Setup:
import sys, os
import clr                 # allows connection to CLR/DLR bits
clr.AddReference("Calico") # the main Calico.exe
import System
import Calico

# Now, define the Document, Engine, and Language classes:
class TestEngine(Calico.Engine):
    def PostSetup(self, calico):
        pass

    def Execute(self, text, feedback=True):
        if feedback:
            print("ok")
        return True

    def ExecuteFile(self, filename):
        print("Run filename '%s'!" % filename)
        return True

    def ReadyToExecute(self, text):
        ## Return True if expression parses ok.
        return True

class TestDocument(Calico.TextDocument):
    def GetAuthors(self):
        return System.Array[str]([
            "Name1 <name1@place.edu>",
            "Name2 <name2@place.edu>"
        ])

class TestLanguage(Calico.Language):
    def __init__(self):
        self.name = "test"
        self.proper_name = "Test"
        self.extensions = System.Array[str](["tt"])
        self.mimetype = "text/plain"
        self.LineComment = "::"

    def MakeEngine(self, manager):
        self.engine = TestEngine(manager)

    def MakeDocument(self, calico, filename):
        return TestDocument(calico, filename, self.name, self.mimetype)

    def GetUseLibraryString(self, fullname):
        pass

# And finally define a method of loading it:
def MakeLanguage():
    return TestLanguage()

In a couple of places we are being careful about exactly the type of item we create (for example, creating an Array rather than a list).

Interactive Development

Of course, you can use Calico as the editor to develop your new language.

To create an TestEngine to interactively try, do this:

First, run the CalicoTest.py inside Calico (eg, open it in Calico and press F5)

Then, at the Shell, enter:

python> engine = TestEngine(calico.manager)
Ok

Now, you can call the methods interactively:

python> engine.Execute("blah blah blah", True)
ok
True
Ok

You can even replace the loaded Test Engine in this manner:

python> calico.manager["test"].engine = engine
Ok

Step By Step Example

Calico looks into its global and local Calico/languages folders to load languages.

For the rest of this document, we assume that you are either working in your own version of Calico, or you are working in your local folder.

To make your own language, you should make sure there is a “languages” subdirectory there. This folder will hold additional languages to use in Calico. Inside the “languages” directory, make a new folder named “HelloWorld”. Finally, add a file named “CalicoHelloWorld.py” in the calico/languages/HelloWorld folder. The “Calico” at the start of the file is important: it tells Calico to try to load this file. You can have other support files in this folder, they just should not start with “Calico”.

Put the following into the file CalicoHelloWorld.py:

# File: calico/languages/HelloWorld/CalicoHelloWorld.py
import sys, os
import clr                 # allows connection to CLR/DLR bits
clr.AddReference("Calico") # the main Calico.exe
import System
import Calico

# Now, define the Document, Engine, and Language classes:
class HelloWorldEngine(Calico.Engine):
    def PostSetup(self, calico):
        pass

    def Execute(self, text, feedback=True):
        if feedback:
            print("ok")
        return True

    def ExecuteFile(self, filename):
        print("Run filename '%s'!" % filename)
        return True

    def ReadyToExecute(self, text):
        ## Return True if expression parses ok.
        return True

class HelloWorldDocument(Calico.TextDocument):
    def GetAuthors(self):
        return System.Array[str]([
            "Name1 <name1@place.edu>",
            "Name2 <name2@place.edu>"
        ])

class HelloWorldLanguage(Calico.Language):
    def __init__(self):
        self.name = "helloworld"
        self.proper_name = "HelloWorld"
        self.extensions = System.Array[str](["hw"])
        self.mimetype = "text/x-helloworld"
        self.LineComment = "::"

    def MakeEngine(self, manager):
        self.engine = HelloWorldEngine(manager)

    def MakeDocument(self, calico, filename):
        return HelloWorldDocument(calico, filename, self.name, self.mimetype)

    def GetUseLibraryString(self, fullname):
        pass

# And finally define a method of loading it:
def MakeLanguage():
    return HelloWorldLanguage()

To activate your language, you first need to start Calico, and check the “HelloWorld” language under menu -> Calico -> Languages. Then quit and restart Calico.

First, test this to make sure HelloWorld works. It only prints out “Ok”. Then, change the CalicoHelloWorld.py to be a language that actually does something. Some examples are:

  • BASIC
  • Logo
  • Forth
  • LOLCAT
  • PostScript

But, you can also make up your own language. The heart of your language will be in the Execute() method. We won’t be doing any fancy parsing yet, so make this simple. For example, in Execute:

for line in text.split("\n"):          # split the text into lines
    if " "  in line:
        command, rest = line.split(" ", 1) # split the line on spaces, but just the first
        if command == "print":
            print(rest)
        elif command == "let":
            var, value = rest.split("=")
            self.variables[var.trim()] = value.trim()
        else:
            raise Exception("I don’t know how to '%s'" % command)

To use this code, you'll need to:

   self.variables = {}

somewhere before using. Putting initialization code in the Engine.PostSetup method is a good place.

Change the names of files and folders to correctly use your languages’ name (e.g., rename “HelloWorldDocument” to “BasicDocument”, etc.)

To add coloring to your language, you will need a Syntax Mode file. For all Calico users, Syntax Mode files are found in the Calico/bin/SyntaxModes/ folder, but you can have your own for your own local languages. The Syntax Mode files use XML for defining languages. For example:

 <SyntaxMode name = "Scheme" mimeTypes="text/x-helloworld">
	<Property name="LineComment">;</Property>
	<Property name="StringQuote">"</Property>
	<EolSpan color = "comment" rule="Comment" tagColor="comment.tag">;</EolSpan>
	…
 </SyntaxMode>

See http://svn.cs.brynmawr.edu/viewvc/Calico/trunk/bin/SyntaxModes/ for additional examples of SyntaxMode files.

Put this file in a folder inside your language folder (eg, languages/Test/SyntaxModes/TestSyntaxMode.xml). It must end in "...SyntaxMode.xml".

You should make sure that your Language’s mimetype matches the mimeType of the Syntax Mode file. You now need to make sure that the TextEditor can find this SyntaxMode file. One way is to put it in the global Calico/bin/SyntaxModes folder. Another way is to load it from your own folder. To do that, add the following before you define any classes in your CalicoHelloWorld.py file:

clr.AddReference("Mono.TextEditor") 
import Mono.TextEditor

and change the definition of MakeDocument to include the path to the SyntaxMode.xml file, which is based on the os.path.dirname of of the executing Python file:

   def MakeDocument(self, calico, filename):
       Mono.TextEditor.Highlighting.SyntaxModeService.LoadStylesAndModes(
           os.path.join(os.path.dirname(__file__), "SyntaxModes"))
       return HelloWorldDocument(calico, filename, self.name, self.mimetype)

Extended Examples