Skip to content

C Mode in Literate Style

Anthony Bau edited this page Feb 2, 2018 · 11 revisions

This document is an explanation of how to write a Droplet parser, by means of writing languages/c.coffee in literate style.

Contents

Introduction

The flow of data in the Droplet parsing pipeline for C mode goes like so:

  1. antlr.coffee takes text and an ANTLR grammar and generates a parse tree in a specific format.
  2. treewalk.coffee takes a parse tree in a specific format and a configuration object (which comes from c.coffee, this file) and generates "markup". This markup consists of specifiers like "blockStart" and "blockEnd" and describes how the Droplet block document should look.
  3. parser.coffee takes that markup and assembles an live Droplet document, where tokens contain pointers to one another and keep an undo stack.

To assemble this pipeline, c.coffee does the following: it imports parser.coffee, which provides a function wrapParser. It also imports antlr.coffee, which provides a function createANTLRParser. It then invokes: wrapParser(createANTLRParser('C', config)), which creates an end-to-end parser factory from its configuration object.

Imports

First, we require parser.coffee and antlr.coffee. We also require helper.coffee as it contains some useful string manipulations. Finally, we require model.coffee, which contains the definitions of functions for manipulating live Droplet documents. We need this in order to do the appropriate mutations when mutation buttons are clicked.

helper = require '../helper.coffee'
parser = require '../parser.coffee'
antlrHelper = require '../antlr.coffee'

model = require '../model.coffee'

For convenience, we unpack the string manipulation functions from helper.coffee into the global namespace. These are fixQuotedString, looseCUnescape and quoteAndCEscape. looseCUnescape resolves backslash-escapes (e.g. \n into a newline), whereas quoteAndCEscape escapes special characters into backslash-escapes and also surrounds the string in quotes. fixQuotedString is the composition of those two, and is used to "fix" bad strings that students might type into string fields.

{fixQuotedString, looseCUnescape, quoteAndCEscape} = helper

Buttons

In our later rules object, we're going to say that some blocks should have buttons attached to them. Buttons are specified by arrays of options objects; each element of the array corresponds to one button at the end of the block. In C mode, we only have four different configurations of buttons we use. We define them here for reuse.

The options available in the options object are key (required), glyph (required), and border (default false). key should be a string that identifies what kind of button this is; this can be any string. You will receive this string back in a callback when the button is clicked, so it just needs to contain enough information that you can properly handle the click. glyph should be a single character to display in the button; frequently this will be some Unicode character with a useful symbol. border is currently unused but might in the future specify whether to render the button inside or outside the border of the block.

The four configurations follow. They are ADD_BUTTON (just a right-arrow glyph button), BOTH_BUTTON (both right and left-arrow glyph buttons), ADD_BUTTON_VERT and BOTH_BUTTON_VERT (similar, but with up/down arrows instead of left/right).

ADD_BUTTON = [
  {
    key: 'add-button'
    glyph: '\u25B6'
    border: false
  }
]
BOTH_BUTTON = [
  {
    key: 'subtract-button'
    glyph: '\u25C0'
    border: false
  }
  {
    key: 'add-button'
    glyph: '\u25B6'
    border: false
  }
]

ADD_BUTTON_VERT = [
  {
    key: 'add-button'
    glyph: '\u25BC'
    border: false
  }
]

BOTH_BUTTON_VERT = [
  {
    key: 'subtract-button'
    glyph: '\u25B2'
    border: false
  }
  {
    key: 'add-button'
    glyph: '\u25BC'
    border: false
  }
]

Introduction to The Rules Object

Now, we define the Rules object. This is the primary piece of configuration for any Treewalk-based parser (the ANTLR helper is Treewalk-based; see antlr.coffee and treewalk.coffee). The ANTLR helper creates a parse tree using the ANTLR grammar and then passes it to treewalk.coffee. treewalk.coffee combines that parse tree with the following Rules object to create block markup for parser.coffee to use.

The Rules object, broadly, maps node names, like expression to types, like block, socket, or indent. Treewalk will walk down the tree and, upon reaching each node, look up its corresponding type in the Rules object and output Droplet markup accordingly.

By default, it is assumed that most nodes are type block; Droplet should put a block around the text they span. The other possible types are:

  • socket; put a text input socket. Later, in a different callback, we can optionally "lock" some sockets making them non-editable unless you click a button.
  • skip; output no markup for this node; act as if its parent has its children directly
  • indent; put a C-shaped "indent" around this node, which can contain multiple children in vertical succession
  • parens; a special kind of block. This will absorb the text of this node into the block defined by its closest child. This way, text like ((((a + b)))) are a single block, rather than four nested ones, and it will have the color and droppability properties of an addition block rather than a "parenthesis" block.

The Rules object can map a node type to a callback, instead, if that node type wants to have different types in different situations. The callback will receive a pointer to the node and can walk up and down the tree as necessary to return the correct type.

The indentContext option

Additionally, nodes of type indent have an optional indentContext option, which is used to specify what kinds of blocks can be dropped into the indent.

ANTLR automatically determines droppability by direct examination of the ANTLR grammar; if it's possible for one node to be parsed as another node (e.g. an "additiveExpression" is an "expression") according to the ANTLR grammar definition (e.g. if there's a rule like expression: additiveExpression* mulExpression?, which makes the expansion expression -> additiveExpression valid), then it allows it to be dropped in that other node's place. See tools/grammar-analysis for more details on this.

This automatic inference fails, however, for indents, which are supposed to be able to take multiple children at once. As such, you can specify what kind of node should be droppable in them. This should be the most general possible node type, so that the automatic inference can work properly.

The buttons option

Nodes of type block can also optionally take a buttons option, which returns an array of configuration objects as discussed above in the buttons section.

Our Rules Object

RULES = {
  # Indents

Block Statements

First, two indent cases: compoundStatement (that's most blocks, containing multiple semicolon'd statements), and structDeclarationsBlock (that's the inside of a struct definition, which can contain only declarations and not any other kind of statement).

  'compoundStatement': {
    'type': 'indent',
    'indentContext': 'blockItemList',
  },
  'structDeclarationsBlock': {
    'type': 'indent',
    'indentContext': 'structDeclaration'
  },

Insides of Switch Statements

Then, some special code to deal with switch statements. switchBlockItemList is the inside of a case. The if statement here in the callback is actually unnecessary; this can be changed to be a constant mapping.

  'switchBlockItemList': (node) ->
    if node.parent?.type is 'switchCase' then {
      'type': 'indent',
      'indentContext': 'switchBlockItemList'
    } else 'skip'
  'switchCompoundStatement': 'skip'
  'switchCaseList': 'skip'
  'switchCase': 'skip'
  'switchLabel': 'skip'

Paren-like Cases

Several different paren cases.

  # Parens
  'expressionStatement': 'parens',
  'primaryExpression': 'parens',
  'structDeclaration': 'parens',

Redundant Nodes

Several skip cases.

  #'declarator': 'skip',
  'directDeclarator': 'skip',
  ###(node) ->
    if not node.parent? or (node.parent.type is 'declarator' and
       node.parent.parent?.type isnt 'functionDefinition' and
       node.parent.children.length is 1)
      'block'
    else
      'skip'###

  # Skips
  'structDeclaratorList': 'skip',
  'blockItemList': 'skip',
  'macroParamList': 'skip',
  'compilationUnit': 'skip',
  'translationUnit': 'skip',
  #'declarationSpecifier': 'skip',
  #'typeSpecifier': 'skip',
  #'structOrUnionSpecifier': 'skip',
  'structDeclarationList': 'skip',
  'rootDeclarator': 'skip',
  'parameterTypeList': 'skip',

Parameter Lists

Parameter lists need special behavior. A parameter list is the list of parameters in a method definition. You need to be able to modify this to have variable length. Thus, it needs add/subtract mutation buttons. If the parameter list only contains "void", then we add only an "add" button. Otherwise, we add both an add and subtract button to allow you to change the number of parameters. (No parameter list, and therefore no buttons, appears if you define a method like int mymethod(), but this is against CS50 style anyway).

  'parameterList': (node) ->
    if node.parent?.type is 'parameterList'
      return 'skip'
    else if node.children.length is 1 and node.children[0].children[0].children[0].children[0].children[0].type is 'Void'
      {type: 'buttonContainer', buttons: ADD_BUTTON}
    else
      {type: 'buttonContainer', buttons: BOTH_BUTTON}

Redundant Nodes in Function Declarations

Some more skips.

  'argumentExpressionList': 'skip'
  'initializerList': 'skip',
  'initDeclarator': 'skip',
  'initDeclaratorList': 'skip'

Declarations

Declarations also need to have buttons on them, since they can support multiple variables, e.g. int a = 1, b = 2, and you should be able to change the number of variables being declared. TODO: this applies to typedefs too and shouldn't.

  'declaration': (node) ->
    if node.children.length is 3 and node.children[1].children.length is 3
      return {type: 'block', buttons: BOTH_BUTTON}
    else if node.children.length is 3
      return {type: 'block', buttons: ADD_BUTTON}
    else if node.children.length is 2 and node.children[1].type is 'Semi'
      return 'parens'
    else
      return 'block'

Initializers

An initializer is either an expression or an array initialization (like {1, 2, 3}). When it is an array initialization, we need to add mutation buttons to it (we check this by checking wither it starts with {). Otherwise, we treat it normally.

  'initializer': (node) ->
    if node.children[0].data?.text is '{'
      if node.children[1].children.length > 2
        return {type: 'block', buttons: BOTH_BUTTON}
      else
        return {type: 'block', buttons: ADD_BUTTON}
    else
      return 'block'

Expressions

An expression can sometimes be a comma-delimited list, like int a = (1, 2, 3);, which I believe assigns a to 3 after evaluating 1 and 2. When it is a comma-delimited list, we allow you to add more expression to it; otherwise, we treat it normally.

  'expression': (node) ->
    if node.parent?.type is 'expression'
      'skip'
    else if node.children[0].type is 'expression'
      {type: 'block', buttons: ADD_BUTTON}
    else
      return 'block'

Conditionals and Switches

A selectionStatement is an if () { } statement or a switch statement. They both have the same node type. Also, we want a special behavior where several nested if/else/if/else blocks get combined into one comb-shaped block. Thus we need a relatively long callback here.

We check to see if we are an if statement by looking at the first token in our span. Then we check to see if our parent is an if statement and we are in the else of it, in which case we don't make a new block for us (to get the "comb-shaped block" behavior). If we are an if statement, we also add mutation buttons that allow the student to add or remove else if blocks. Note that we don't have to worry about the indents here because that will be covered by the compoundStatement node that is the child of the selectionStatement node.

If we are a switch statement, then we can just make a block with some mutation buttons, since we will get the comb-shaped block behavior automatically.

  # Special: nested selection statement. Skip iff we are an if statement
  # in the else clause of another if statement.
  'selectionStatement': (node) ->
    if node.children[0].data.text is 'if' and
       # Check to make sure the parent is an if. Actually,
       # our immediate parent is a 'statement'; the logical wrapping
       # parent is parent.parent
       node.parent?.type is 'statement' and
       node.parent.parent?.type is 'selectionStatement' and
       node.parent.parent.children[0].data?.text is 'if' and
       # This last one will only occur if we are the else clause
       node.parent is node.parent.parent.children[6]
      return 'skip'
    # Otherwise, if we are an if statement, give us a mutation button
    else if node.children[0].data.text is 'if'
      if node.children.length is 7
        return {type: 'block', buttons: BOTH_BUTTON_VERT}
      else
        return {type: 'block', buttons: ADD_BUTTON_VERT}
    else if node.children[0].data.text is 'switch'
      return {
        type: 'block', buttons: [
          {
            key: 'subtract-button-switch'
            glyph: '\u25B2'
            border: false
          },
          {
            key: 'add-button-switch'
            glyph: '\u25BC'
            border: false
          }
        ]
      }
    else
      return 'block'

Function Calls

Function calls need to be handled specially to be aware of the different function prototypes. The second argument to this callback will be the modeOptions object that the embedder passes in, which will contain a list of known functions and their prototypes. We check against this list and format the block accordingly.

Unfortunately, function calls and increment/decrement all have the same node name, postfixExpression. Thus, we need to figure out whether we are a function call; we do this by determining whether the first token after our first child is an open-paren and our last child is a close-paren.

  # Special: functions
  'postfixExpression': (node, opts) ->
    if (
        node.children.length is 3 and node.children[1].data?.text is '(' and node.children[2].data?.text is ')' or
        node.children.length is 4 and node.children[1].data?.text is '(' and node.children[3].data?.text is ')'
       ) and

We then check against our options object to see whether this block is going to require mutation buttons. The number of arguments is variable if: the function is not a plain name (e.g. doing something like (*comparator)(a, b)), the function's name is not known (not in opts.functions), or the function was specified as having variable arguments in our options object (i.e. it has a minArgs property).

This is very messy because the name of the function is stored in node.children[0].children[0].children[0].data.text due to several layers of nested nodes.

       (
         (not node.children[0].children[0]?.children?[0]?) or
         node.children[0].children[0].children[0].type isnt 'Identifier' or not opts.functions? or
         node.children[0].children[0].children[0].data.text not of opts.functions or
         opts.functions[node.children[0].children[0].children[0].data.text].minArgs?
       )

If we've determined that we want to put add/remove mutation buttons on this function call, then we determine whether we can put a remove button on. Obviously, if there are no current arguments, then we do not put a remove button on. Otherwise, we look up the minArgs option for this function and see if we have more arguments than it; we put a remove button on if and only if we do. Otherwise, if there is no minArgs option and we have >0 arguments, then we do put a remove button on.

      if node.children.length is 3
        return {type: 'block', buttons: ADD_BUTTON}
      else if opts.functions? and node.children[0].children[0].children[0].data?.text of opts.functions
        minimum = opts.functions[node.children[0].children[0].children[0].data.text].minArgs
        nargs = 0
        param = node.children[2]
        while param.type is 'argumentExpressionList'
          nargs += 1
          param = param.children[0]
        if nargs > minimum
          return {type: 'block', buttons: BOTH_BUTTON}
        else
          return {type: 'block', buttons: ADD_BUTTON}
      else
        return {type: 'block', buttons: BOTH_BUTTON}

Otherwise, we do not want to put mutation buttons on this block, so we can just treat it like any other expression.

    else
      return 'block'

The following specialMethodCall node is a hack we had to put into the C.g4 grammar to handle a weird parse case. The parse case is things of type a (b);. This can be legally parsed as a declaration, with a being the type and b being the variable name. However, the parentheses are unnecessary, so usually this actually means a method call. As such, we introduce a new kind of parse node specialMethodCall which has higher priority than a declaration and captures this case.

We do similar reasoning as above here to determine whether to put mutation buttons on.

  'specialMethodCall': (node, opts) ->
    if (
        not opts.functions? or
        node.children[0].data.text not of opts.functions
       )
      return {type: 'block', buttons: BOTH_BUTTON}
    else if opts.functions[node.children[0].data.text].minArgs?
      if opts.functions[node.children[0].data.text].minArgs is 0
        return {type: 'block', buttons: BOTH_BUTTON}
      else
        return {type: 'block', buttons: ADD_BUTTON}
    else
      return 'block'

(There is some dead code here that should be removed.)

  # Special: declarationSpecifiers (type names) should be surrounded by single sockets
  #'declarationSpecifiers': (node) -> 'skip' #if node.parent.type is 'declaration' then 'skip' else 'block'  #socket'
  #'declarationSpecifiers2': (node) -> 'skip' #if node.parent.type is 'declaration' then 'skip' else 'block'  #socket'

Declaration Specifiers

declarationSpecifiers are things like static and const, and also things like typedef, and also types. Each of these should be handled differently. declarationSpecifier nodes that represent a typedef should be ignored; these are cases like typedef int customName, where the node would represent the span typedef int, and it doesn't make sense to put a block around that.

In "simple" cases, like long long or even static long long, we would like to turn the entire declarationSpecifiers span into a single socket, since static long long is kind of like a single type. Thus, if every child is either not a typename or is at typename that is a single token, then we turn into a single socket.

Otherwise, we are some complex type, like (*int)(int, char). In this case, we should display the syntax tree of this complex type using blocks, so we turn into a block.

specifierQualifierList and declarationSpecifiers2 are very similar nodes.

  'declarationSpecifiers': (node) ->
    if node.children[0].children[0].type is 'storageClassSpecifier' and
        node.children[0].children[0].children[0].type is 'Typedef'
      'skip'
    else if node.children.every((child) -> child.children[0].type isnt 'typeSpecifier' or child.children[0].children[0].children.length is 0)
      'socket'
    else
      console.log node.children[0].children[0].type, node.children[0].children[0].children
      'block'

  'declarationSpecifiers2': (node) ->
    if node.children.every((child) -> child.children[0].type isnt 'typeSpecifier' or child.children[0].children[0].children.length is 0)
      'socket'
    else
      'block'

  'specifierQualifierList': (node) ->
    if node.children.every((child) -> child.children[0].type isnt 'typeSpecifier' or child.children[0].children[0].children.length is 0)
      'socket'
    else
      'block'

Sockets

Finally, things that should always be in sockets: native type names, identifiers, literals.

  # Sockets
  'Int': 'socket'
  'Void': 'socket'
  'Long': 'socket'
  'Short': 'socket'
  'Float': 'socket'
  'Double': 'socket'
  'Char': 'socket'

  'Identifier': 'socket',
  'StringLiteral': 'socket',
  'SharedIncludeLiteral': 'socket',
  'Constant': 'socket'
}

Color Rules

COLOR_DEFAULTS = {
}

COLOR_RULES = {
  'declarationSpecifiers': 'type'
  'declarationSpecifiers2': 'type'

  'specialFunctionDeclaration': 'function'

  'structOrUnionSpecifier': 'struct' # e.g. `struct a { }`
  'structDeclaration': 'struct' # e.g. `int a;` within `struct a { int a; }`

  'declarator': 'declaration',
  'pointerDeclarator': 'declaration',
  'directDeclarator': 'declaration',
  'specifierQualifierList': 'declaration',# e.g `int a;` when inside `struct {}`
  'declaration': 'declaration', # e.g. `int a;`
  'parameterDeclaration': 'declaration', # e.g. `int a` when in `int myMethod(int a) { }`
  'externalDeclaration': 'declaration', # e.g. `int a = b` when global

  'functionDefinition': 'function', # e.g. `int myMethod() { }`

  'expression': 'value', # Any expression, like `a + b`
  'additiveExpression': 'value', # e.g. `a + b`
  'multiplicativeExpression': 'value', # e.g. `a * b`
  'unaryExpression': 'value', # e.g. `sizeof(a)`
  'typeName': 'value', # e.g. `int`
  'initializer': 'value', # e.g. `{a, b, c}` when in `int x[] = {a, b, c};`
  'castExpression': 'value' # e.g. `(b)a`

  'equalityExpression': 'logic' # e.g. `a == b`
  'relationalExpression': 'logic' # e.g. `a == b`
  'logicalAndExpression': 'logic', # e.g. `a && b`
  'logicalOrExpression': 'logic', # e.g. `a || b`

  'jumpStatement': 'return', # e.g. `return 0;`

  'postfixExpression': 'value', # e.g. `a(b, c);` OR `a++`
  'assignmentExpression': 'assign', # e.g. `a = b;` OR `a = b`
  'specialMethodCall': 'functionCall', # e.g. `a(b);`
  #'initDeclarator': 'assign', # e.g. `a = b` when inside `int a = b;`

  'iterationStatement': 'control', # e.g. `for (int i = 0; i < 10; i++) { }`
  'selectionStatement': 'control', # e.g. if `(a) { } else { }` OR `switch (a) { }`
  #'blockItemList': 'control', # List of commands
  'compoundStatement': 'control', # List of commands inside braces
  'declarationSpecifier': 'control', # e.g. `int` when in `int a = b;`
}

Shape Rules

SHAPE_RULES = {
  'blockItem': helper.BLOCK_ONLY, # Any statement, like `return 0;`
  'expression': helper.VALUE_ONLY, # Any expression, like `a + b`
  'postfixExpression': helper.VALUE_ONLY, # e.g. `a(b, c);` OR `a++`
  'equalityExpression': helper.VALUE_ONLY, # e.g. `a == b`
  'logicalAndExpression': helper.VALUE_ONLY, # e.g. `a && b`
  'logicalOrExpression': helper.VALUE_ONLY, # e.g. `a || b`
  'iterationStatement': helper.BLOCK_ONLY, # e.g. `for (int i = 0; i < 10; i++) { }`
  'selectionStatement': helper.BLOCK_ONLY, # e.g. if `(a) { } else { }` OR `switch (a) { }`
  'assignmentExpression': helper.BLOCK_ONLY, # e.g. `a = b;` OR `a = b`
  'relationalExpression': helper.VALUE_ONLY, # e.g. `a < b`
  'initDeclarator': helper.BLOCK_ONLY, # e.g. `a = b` when inside `int a = b;`
  'externalDeclaration': helper.BLOCK_ONLY, # e.g. `int a = b` when global
  'structDeclaration': helper.BLOCK_ONLY, # e.g. `struct a { }`
  'declarationSpecifier': helper.BLOCK_ONLY, # e.g. `int` when in `int a = b;`
  'statement': helper.BLOCK_ONLY, # Any statement, like `return 0;`
  'functionDefinition': helper.BLOCK_ONLY, # e.g. `int myMethod() { }`
  'expressionStatement': helper.BLOCK_ONLY, # Statement that consists of an expression, like `a = b;`
  'additiveExpression': helper.VALUE_ONLY, # e.g. `a + b`
  'multiplicativeExpression': helper.VALUE_ONLY, # e.g. `a * b`
  'declaration': helper.BLOCK_ONLY, # e.g. `int a;`
  'parameterDeclaration': helper.BLOCK_ONLY, # e.g. `int a` when in `int myMethod(int a) { }`
  'unaryExpression': helper.VALUE_ONLY, # e.g. `sizeof(a)`
  'typeName': helper.VALUE_ONLY, # e.g. `int`
  'initializer': helper.VALUE_ONLY, # e.g. `{a, b, c}` when in `int x[ = {a, b, c};`
  'castExpression': helper.VALUE_ONLY # e.g. `(b)a`
}

Dropdowns

NATIVE_TYPES =[
  'int'
  'char'
  'double'
  'long long'
  'string'
  'bool'
  'FILE'
  'float'
]
DROPDOWNS = {
  'specifierQualifierList': NATIVE_TYPES
  'declarationSpecifiers': NATIVE_TYPES
  'declarationSpecifiers2': NATIVE_TYPES
}

Creating the config object.

config = {
  RULES, COLOR_RULES, SHAPE_RULES, COLOR_DEFAULTS, DROPDOWNS
}

Parenthesis-Wrapping Rules

ADD_PARENS = (leading, trailing, node, context) ->
  leading '(' + leading()
  trailing trailing() + ')'

ADD_SEMICOLON = (leading, trailing, node, context) ->
  trailing trailing() + ';'

REMOVE_SEMICOLON = (leading, trailing, node, context) ->
  trailing trailing().replace /\s*;\s*$/, ''

config.PAREN_RULES = {
  'primaryExpression': {
    'expression': ADD_PARENS
  },
  'expressionStatement': {
    'expression': ADD_SEMICOLON
  }
  'postfixExpression': {
    'specialMethodCall': REMOVE_SEMICOLON
  }
  ###
  # These two rules seem to be wrong and I don't
  # remember why I put them here in the first place.
  # Commenting out for now.
  'declaration': {
    'declarationSpecifiers': ADD_SEMICOLON
  }
  'structDeclaration': {
    'specifierQualifierList': ADD_SEMICOLON
  }
  ###
}

Socket Locking Rules

# Test to see if a node is a method call
getMethodName = (node) ->
  if node.type is 'postfixExpression' and
     # The children of a method call are either
     # `(method) '(' (paramlist) ')'` OR `(method) '(' ')'`
     node.children.length in [3, 4] and
     # Check to make sure that the correct children are parentheses
     node.children[1].type is 'LeftParen' and
     (node.children[2].type is 'RightParen' or node.children[3]?.type is 'RightParen') and
     # Check to see whether the called method is a single identifier, like `puts` in
     # `getc()`, rather than `getFunctionPointer()()` or `a.b()`
     node.children[0].children[0].type is 'primaryExpression' and
     node.children[0].children[0].children[0].type is 'Identifier'

    # If all of these are true, we have a function name to give
    return node.children[0].children[0].children[0].data.text

  # Alternatively, we could have the special `a(b)` node.
  else if node.type is 'specialMethodCall'
    return node.children[0].data.text

  return null

config.SHOULD_SOCKET = (opts, node) ->
  # We will not socket if we are the identifier
  # in a single-identifier function call like `a(b, c)`
  # and `a` is in the known functions list.
  #
  # We can only be such an identifier if we have the appropriate number of parents;
  # check.
  if ((node.parent? and node.parent.parent? and node.parent.parent.parent?) or
      node.parent?.type is 'specialMethodCall') and

      (node.parent?.type is 'specialMethodCall' or getMethodName(node.parent.parent.parent)? and

      # Check to see whether we are the first child
      node.parent.parent is node.parent.parent.parent.children[0] and
      node.parent is node.parent.parent.children[0] and
      node is node.parent.children[0])

    # If the checks pass, do not socket.
    return {
      type: 'locked'
      dropdown: if opts.functions? then generateDropdown(opts.functions) else null
    }

  # We will do a locked socket for all type declarations
  else if node.type in ['declarationSpecifiers', 'declarationSpecifiers2', 'Int', 'Long', 'Short', 'Float', 'Double', 'Char']
    return {
      type: 'locked'
      dropdown: NATIVE_TYPES
    }
  else if (node.type is 'Void' and node.parent.type is 'typeSpecifier' and node.parent.parent.parent.parent.type is 'parameterDeclaration')
    return false

  else if (node.type is 'Identifier' and node.parent.type is 'typedefName') or (node.type is 'Void' and node.parent.type is 'typeSpecifier' and node.parent.parent.parent.parent.type isnt 'parameterDeclaration')
    return {
      type: 'locked'
      dropdown: NATIVE_TYPES
    }

  else
    return true

generateDropdown = (knownFunctions) ->
  result = []
  for func, val of knownFunctions
    result.push func
  result.sort()
  return result

Color and Shape Rule Exceptions

# Color and shape callbacks look up the method name
# in the known functions list if available.
config.COLOR_CALLBACK = (opts, node) ->
  return null unless opts.functions?

  if node.type is 'declarationSpecifiers' and node.children[0].children[0].children[0].type is 'Typedef'
    return 'declaration'

  name = getMethodName node

  if name?
    return 'functionCall'
  else
    return null

config.SHAPE_CALLBACK = (opts, node) ->
  return null unless opts.functions?

  if node.type is 'postfixExpression'
    name = getMethodName node

    if name?
      return helper.BLOCK_ONLY
    else
      return helper.VALUE_ONLY
  else
    return null

Identifying Comments and Preprocessor Directives

config.isComment = (text) ->
  text.match(/^(\s*\/\/.*)|(#.*)$/)?

config.parseComment = (text) ->
  # Try standard comment
  comment = text.match(/^(\s*\/\/)(.*)$/)
  if comment?
    sockets =  [
      [comment[1].length, comment[1].length + comment[2].length]
    ]
    color = 'comment'

    return {sockets, color}

  if text.match(/^#\s*((?:else)|(?:endif))$/)
    sockets =  []
    color = 'purple'

    return {sockets, color}

  # Try #define directive
  binary = text.match(/^(#\s*(?:(?:define))\s*)([a-zA-Z_][0-9a-zA-Z_]*)(\s+)(.*)$/)
  if binary?
    sockets =  [
      [binary[1].length, binary[1].length + binary[2].length]
      [binary[1].length + binary[2].length + binary[3].length, binary[1].length + binary[2].length + binary[3].length + binary[4].length]
    ]
    color = 'purple'

    return {sockets, color}

  # Try functional #define directive.
  binary = text.match(/^(#\s*define\s*)([a-zA-Z_][0-9a-zA-Z_]*\s*\((?:[a-zA-Z_][0-9a-zA-Z_]*,\s*)*[a-zA-Z_][0-9a-zA-Z_]*\s*\))(\s+)(.*)$/)
  if binary?
    sockets =  [
      [binary[1].length, binary[1].length + binary[2].length]
      [binary[1].length + binary[2].length + binary[3].length, binary[1].length + binary[2].length + binary[3].length + binary[4].length]
    ]
    color = 'purple'

    return {sockets, color}

  # Try any of the unary directives: #define, #if, #ifdef, #ifndef, #undef, #pragma
  unary = text.match(/^(#\s*(?:(?:define)|(?:ifdef)|(?:if)|(?:ifndef)|(?:undef)|(?:pragma))\s*)(.*)$/)
  if unary?
    sockets =  [
      [unary[1].length, unary[1].length + unary[2].length]
    ]
    color = 'purple'

    return {sockets, color}

  # Try #include, which must include the quotations
  unary = text.match(/^(#\s*include\s*<)(.*)>\s*$/)
  if unary?
    sockets =  [
      [unary[1].length, unary[1].length + unary[2].length]
    ]
    color = 'purple'

    return {sockets, color}

  unary = text.match(/^(#\s*include\s*")(.*)"\s*$/)
  if unary?
    sockets =  [
      [unary[1].length, unary[1].length + unary[2].length]
    ]
    color = 'purple'

    return {sockets, color}

Configuration for Dealing with String Literals

config.getDefaultSelectionRange = (string) ->
  start = 0; end = string.length
  if string.length > 1 and string[0] is string[string.length - 1] and string[0] is '"'
    start += 1; end -= 1
  if string.length > 1 and string[0] is '<' and string[string.length - 1] is '>'
    start += 1; end -= 1
  if string.length is 3 and string[0] is string[string.length - 1] is '\''
    start += 1; end -= 1
  return {start, end}

config.stringFixer = (string) ->
  if /^['"]|['"]$/.test string
    return fixQuotedString [string]
  else
    return string

config.empty = '_'
config.EMPTY_STRINGS = {
  'Identifier': '_'
  'declaration': '_ _'
  'statement': '_;'
}
config.emptyIndent = ''

insertAfterLastSocket = (str, block, insert) ->

  {string: suffix} = model.stringThrough(
    block.end,
    ((token) -> token.parent is block and token.type is 'socketEnd'),
    ((token) -> token.prev)
  )

  prefix = str[...-suffix.length]

  return prefix + insert + suffix

removeLastSocketAnd = (str, block, prefixClip = null, suffixClip = null) ->
  {string: suffix, token: socketToken} = model.stringThrough(
    block.end,
    ((token) -> token.parent is block and token.type is 'socketEnd'),
    true
  )

  if suffixClip?
    suffix = suffix.replace suffixClip, ''

  {string: prefix} = model.stringThrough(
    block.start,
    ((token) -> token.container is socketToken.container),
    false
  )

  if prefixClip?
    prefix = prefix.replace prefixClip, ''

  return prefix + suffix

Mutation Button Actions

config.handleButton = (str, type, block) ->
  blockType = block.nodeContext?.type ? block.parseContext

  if type is 'add-button-switch'

    indents = []
    block.traverseOneLevel (child) ->
      if child.type is 'indent'
        indents.push child
    indent = indents[indents.length - 1]
    preindent = indents[indents.length - 2]

    # Determine if the last indent is a default
    # or a case
    if preindent?
      {string: infix} = model.stringThrough(
        preindent.end,
        ((token) -> token is indent.start),
        false
      )

      # If it's a case, go ahead and append
      # at the end. Otherwise, append
      # before the end.
      if infix.indexOf('case') is -1
        indent = preindent

    {string: prefix} = model.stringThrough(
      block.start,
      ((token) -> token is indent.end),
      false
    )

    suffix = str[prefix.length + 1...]

    return prefix + '\n  case n:\n    break;\n' + suffix

  else if type is 'subtract-button-switch'

    indents = []
    block.traverseOneLevel (child) ->
      if child.type is 'indent'
        indents.push child
    indents = indents[-3...]

    if indents.length is 1
      return str

    if indents.length is 3
      {string: infix} = model.stringThrough(
        indents[1].end,
        ((token) -> token is indents[2].start),
        false
      )

      if infix.indexOf('case') >= 0
        indents.shift()

    {string: prefix} = model.stringThrough(
      block.start,
      ((token) -> token is indents[0].end),
      false
    )

    {string: suffix} = model.stringThrough(
      block.end,
      ((token) -> token is indents[1].end),
      true
    )

    return prefix + suffix

  if type is 'add-button'

    if blockType is 'selectionStatement'
      indents = []
      block.traverseOneLevel (child) ->
        if child.type is 'indent'
          indents.push child
      indents = indents[-3...]

      if indents.length is 1
        return str + '\nelse\n{\n  \n}\n'

      else if indents.length is 2
        {string: prefix} = model.stringThrough(
          block.start,
          ((token) -> token is indents[0].end),
          false
        )

        suffix = str[prefix.length + 1...]

        return prefix + '\n}\nelse if (a == b)\n{\n  \n' + suffix

      else if indents.length is 3
        {string: prefix} = model.stringThrough(
          block.start,
          ((token) -> token is indents[1].end),
          false
        )

        suffix = str[prefix.length + 1...]

        return prefix + '\n}\nelse if (a == b)\n{\n  \n' + suffix

    else if blockType is 'postfixExpression' or blockType is 'specialMethodCall'
      if str.match(/\(\s*\)\s*;?$/)?
        return str.replace(/(\s*\)\s*;?\s*)$/, "#{config.empty}$1")
      else
        return str.replace(/(\s*\)\s*;?\s*)$/, ", #{config.empty}$1")
    else if blockType is 'parameterList'
      if str.match(/^\s*void\s*$/)
        return "type param"
      return str + ", type param"
    else if blockType is 'declaration'
        return str.replace(/(\s*;?\s*)$/, ", #{config.empty} = #{config.empty}$1")
    else if blockType is 'initializer'
      return str.replace(/(\s*}\s*)$/, ", #{config.empty}$1")
    else if blockType is 'expression'
      return str.replace(/(\s*;\s*)?$/, ", #{config.empty}$1")


  else if type is 'subtract-button'

    if blockType is 'selectionStatement'
      indents = []
      block.traverseOneLevel (child) ->
        if child.type is 'indent'
          indents.push child
      indents = indents[-3...]

      if indents.length is 1
        return str

      else
        {string: prefix} = model.stringThrough(
          block.start,
          ((token) -> token is indents[0].end),
          false
        )

        {string: suffix} = model.stringThrough(
          block.end,
          ((token) -> token is indents[1].end),
          true
        )

        return prefix + suffix

    else if blockType is 'postfixExpression' or blockType is 'specialMethodCall'
      return removeLastSocketAnd str, block, /\s*,\s*$/
    else if blockType is 'declaration'
      reparsed = config.__antlrParse 'declaration', str

      lines = str.split '\n'
      prefix = helper.clipLines lines, {line: 0, column: 0}, reparsed.children[1].children[2].bounds.start
      suffix = str.match(/\s*;?\s*$/)[0]

      prefix = prefix.replace /\s*,\s*$/, ''

      return prefix + suffix
    else if blockType is 'initializer'
      return removeLastSocketAnd str, block, /\s*,\s*$/
    else if blockType is 'parameterList'
      count = 0
      block.traverseOneLevel (x) -> if x.type is 'socket' then count++
      if count is 1
        return 'void'
      else
        return removeLastSocketAnd str, block, /\s*,\s*$/
    else if blockType is 'expression'
      return removeLastSocketAnd str, block, /\s*,\s*$/

  return str

config.rootContext = 'translationUnit'

config.lockedSocketCallback = (opts, socketText, parentText, parseContext) ->
  if opts.functions? and socketText of opts.functions and 'prototype' of opts.functions[socketText]
    if parseContext in ['expressionStatement', 'specialMethodCall']
      return opts.functions[socketText].prototype
    else
      return opts.functions[socketText].prototype.replace /;$/, ''
  else
    return parentText

Conclusion

module.exports = parser.wrapParser antlrHelper.createANTLRParser 'C', config