-
Notifications
You must be signed in to change notification settings - Fork 84
C Mode in Literate Style
This document is an explanation of how to write a Droplet parser, by means of writing languages/c.coffee
in literate style.
- Introduction
- Introduction to the Rules Object
- Our Rules Object
- Color Rules
- Shape Rules
- Dropdown Rules
- Parenthesis-Wrapping Rules
- Socket Locking Rules
- Exceptions to Color and Shape Rules
- Identifying Comments and Preprocessor Directives
- Mutation Button Actions
The flow of data in the Droplet parsing pipeline for C mode goes like so:
-
antlr.coffee
takes text and an ANTLR grammar and generates a parse tree in a specific format. -
treewalk.coffee
takes a parse tree in a specific format and a configuration object (which comes fromc.coffee
, this file) and generates "markup". This markup consists of specifiers like "blockStart" and "blockEnd" and describes how the Droplet block document should look. -
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.
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
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
}
]
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.
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.
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.
RULES = {
# Indents
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'
},
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'
Several different paren cases.
# Parens
'expressionStatement': 'parens',
'primaryExpression': 'parens',
'structDeclaration': 'parens',
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 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}
Some more skips.
'argumentExpressionList': 'skip'
'initializerList': 'skip',
'initDeclarator': 'skip',
'initDeclaratorList': 'skip'
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'
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'
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'
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 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'
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'
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_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 = {
'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`
}
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
}
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
}
###
}
# 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 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
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}
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
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
module.exports = parser.wrapParser antlrHelper.createANTLRParser 'C', config