mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-26 13:37:03 +00:00
889 lines
31 KiB
JavaScript
889 lines
31 KiB
JavaScript
/*
|
|
* preprocessor.js
|
|
* Objective-J
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Copyright 2008, 280 North, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
OBJJ_PREPROCESSOR_DEBUG_SYMBOLS = 1 << 0;
|
|
|
|
function objj_preprocess(/*String*/ aString, /*objj_bundle*/ aBundle, /*objj_file*/ aSourceFile, /*unsigned*/ flags)
|
|
{
|
|
try
|
|
{
|
|
return new objj_preprocessor(aString, aSourceFile, aBundle, flags).fragments();
|
|
}
|
|
catch (anException)
|
|
{
|
|
objj_exception_report(anException, aSourceFile);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
OBJJParseException = "OBJJParseException";
|
|
OBJJClassNotFoundException = "OBJJClassNotFoundException";
|
|
|
|
var TOKEN_ACCESSORS = "accessors",
|
|
TOKEN_CLASS = "class",
|
|
TOKEN_END = "end",
|
|
TOKEN_FUNCTION = "function",
|
|
TOKEN_IMPLEMENTATION = "implementation",
|
|
TOKEN_IMPORT = "import",
|
|
TOKEN_NEW = "new",
|
|
TOKEN_SELECTOR = "selector",
|
|
TOKEN_SUPER = "super",
|
|
|
|
TOKEN_EQUAL = '=',
|
|
TOKEN_PLUS = '+',
|
|
TOKEN_MINUS = '-',
|
|
TOKEN_COLON = ':',
|
|
TOKEN_COMMA = ',',
|
|
TOKEN_PERIOD = '.',
|
|
TOKEN_ASTERISK = '*',
|
|
TOKEN_SEMICOLON = ';',
|
|
TOKEN_LESS_THAN = '<',
|
|
TOKEN_OPEN_BRACE = '{',
|
|
TOKEN_CLOSE_BRACE = '}',
|
|
TOKEN_GREATER_THAN = '>',
|
|
TOKEN_OPEN_BRACKET = '[',
|
|
TOKEN_DOUBLE_QUOTE = '"',
|
|
TOKEN_PREPROCESSOR = '@',
|
|
TOKEN_CLOSE_BRACKET = ']',
|
|
TOKEN_QUESTION_MARK = '?',
|
|
TOKEN_OPEN_PARENTHESIS = '(',
|
|
TOKEN_CLOSE_PARENTHESIS = ')';
|
|
|
|
#define IS_WORD(token) /^\w+$/.test(token)
|
|
|
|
#define IS_NOT_EMPTY(buffer) buffer.atoms.length !== 0
|
|
#define CONCAT(buffer, atom) buffer.atoms[buffer.atoms.length] = atom
|
|
|
|
var SUPER_CLASSES = new objj_dictionary();
|
|
|
|
var OBJJ_CURRENT_BUNDLE = NULL;
|
|
|
|
// FIXME: Used fixed regex
|
|
var objj_lexer = function(aString)
|
|
{
|
|
this._index = 0;
|
|
this._tokens = (aString + '\n').match(/\/\/.*(\r|\n)?|\/\*(?:.|\n|\r)*?\*\/|\w+\b|[+-]?\d+(([.]\d+)*([eE][+-]?\d+))?|"([^"\\]|\\[\s\S])*"|'[^'\\]*(\\.[^'\\]*)*'|\s+|./g);
|
|
|
|
return this;
|
|
}
|
|
|
|
objj_lexer.prototype.next = function()
|
|
{
|
|
return this._tokens[this._index++];
|
|
}
|
|
|
|
objj_lexer.prototype.previous = function()
|
|
{
|
|
return this._tokens[--this._index];
|
|
}
|
|
|
|
objj_lexer.prototype.last = function()
|
|
{
|
|
if (this._index > 1)
|
|
return this._tokens[this._index - 2];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
objj_lexer.prototype.skip_whitespace= function()
|
|
{
|
|
var token;
|
|
while((token = this.next()) && (!(/\S/).test(token) || token.substr(0,2) == "//" || token.substr(0,2) == "/*")) ;
|
|
|
|
return token;
|
|
}
|
|
|
|
var objj_stringBuffer = function()
|
|
{
|
|
this.atoms = [];
|
|
}
|
|
|
|
objj_stringBuffer.prototype.toString = function()
|
|
{
|
|
return this.atoms.join("");
|
|
}
|
|
|
|
objj_stringBuffer.prototype.clear = function()
|
|
{
|
|
this.atoms = [];
|
|
}
|
|
|
|
objj_stringBuffer.prototype.isEmpty = function()
|
|
{
|
|
return (this.atoms.length === 0);
|
|
}
|
|
|
|
var objj_preprocessor = function(aString, aSourceFile, aBundle, flags)
|
|
{
|
|
this._currentClass = "";
|
|
this._currentSuperClass = "";
|
|
|
|
this._file = aSourceFile;
|
|
this._fragments = [];
|
|
this._preprocessed = new objj_stringBuffer();
|
|
this._tokens = new objj_lexer(aString);
|
|
this._flags = flags;
|
|
this._bundle = aBundle;
|
|
|
|
this.preprocess(this._tokens, this._preprocessed);
|
|
this.fragment();
|
|
}
|
|
|
|
objj_preprocessor.prototype.fragments = function()
|
|
{
|
|
return this._fragments;
|
|
}
|
|
|
|
objj_preprocessor.prototype.accessors = function(tokens)
|
|
{
|
|
var token = tokens.skip_whitespace(),
|
|
attributes = {};
|
|
|
|
if (token != TOKEN_OPEN_PARENTHESIS)
|
|
{
|
|
tokens.previous();
|
|
|
|
return attributes;
|
|
}
|
|
|
|
while ((token = tokens.skip_whitespace()) != TOKEN_CLOSE_PARENTHESIS)
|
|
{
|
|
var name = token,
|
|
value = true;
|
|
|
|
if (!IS_WORD(name))
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** @property attribute name not valid."));
|
|
|
|
if ((token = tokens.skip_whitespace()) == TOKEN_EQUAL)
|
|
{
|
|
value = tokens.skip_whitespace();
|
|
|
|
if (!IS_WORD(value))
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** @property attribute value not valid."));
|
|
|
|
if (name == "setter")
|
|
{
|
|
if ((token = tokens.next()) != TOKEN_COLON)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** @property setter attribute requires argument with \":\" at end of selector name."));
|
|
|
|
value += ":";
|
|
}
|
|
|
|
token = tokens.skip_whitespace();
|
|
}
|
|
|
|
attributes[name] = value;
|
|
|
|
if (token == TOKEN_CLOSE_PARENTHESIS)
|
|
break;
|
|
|
|
if (token != TOKEN_COMMA)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Expected ',' or ')' in @property attribute list."));
|
|
}
|
|
|
|
return attributes;
|
|
}
|
|
|
|
objj_preprocessor.prototype.brackets = function(tokens, /*objj_stringBuffer*/ aStringBuffer)
|
|
{
|
|
var buffer = aStringBuffer ? aStringBuffer : new objj_stringBuffer();
|
|
|
|
// We maintain two parallel interpretations through the process,
|
|
// One which assumes the brackets form a literal array, and
|
|
// another that builds the possible message dispatch composed of
|
|
// a receiver, a selector, and a number of marg_list.
|
|
var literal = new objj_stringBuffer(),
|
|
msgSend = "objj_msgSend",
|
|
receiver = "",
|
|
selector = "",
|
|
marg_list = [];
|
|
|
|
CONCAT(literal, '[');
|
|
|
|
// We keep track of the current iterative token, the previous
|
|
// expression, tertiary operations, and parenthesis.
|
|
var token = "",
|
|
array = false,
|
|
previous = "",
|
|
|
|
braces = 0,
|
|
tertiary = 0,
|
|
parenthesis = 0;
|
|
|
|
while((token = tokens.skip_whitespace()) && token != TOKEN_CLOSE_BRACKET)
|
|
{
|
|
// We should attempt to preprocess our message code only if we are
|
|
// not within parenthesis or a tertiary statement.
|
|
var preprocess = !braces && !tertiary && !parenthesis && !array;
|
|
|
|
// We handle the special case where the receiver is super. In this case,
|
|
// use an objj_super object and use objj_msgSendSuper instead of objj_msgSend.
|
|
if (token == TOKEN_SUPER)
|
|
{
|
|
if (!receiver.length)
|
|
{
|
|
msgSend = "objj_msgSendSuper";
|
|
token = "{ receiver:self, super_class:" + this._currentSuperClass + " }";
|
|
}
|
|
else
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Can't use 'super' in this context."));
|
|
}
|
|
|
|
else if (token == TOKEN_OPEN_BRACE)
|
|
++braces;
|
|
|
|
else if (token == TOKEN_CLOSE_BRACE)
|
|
--braces;
|
|
|
|
// Tertiary expressions have the potential to confuse the preprocessor,
|
|
// so keep track of them to avoid misidentifying a colon (':') for an
|
|
// argument.
|
|
else if(token == TOKEN_QUESTION_MARK)
|
|
++tertiary;
|
|
|
|
// Keep track of expressions within parenthesis.
|
|
else if(token == TOKEN_OPEN_PARENTHESIS)
|
|
++parenthesis;
|
|
|
|
else if(token == TOKEN_CLOSE_PARENTHESIS)
|
|
--parenthesis;
|
|
|
|
// If we reach a nested bracket, preprocess it first and interpret
|
|
// the result as the current token.
|
|
else if(token == TOKEN_OPEN_BRACKET)
|
|
token = this.brackets(tokens);
|
|
|
|
else if(token == TOKEN_PREPROCESSOR)
|
|
token = this.directive(tokens);
|
|
|
|
// Preprocess tokens only if we're not within parenthesis or in a
|
|
// tertiary statement.
|
|
if (preprocess)
|
|
{
|
|
// If we ever reach a comma that is not in a sub expression,
|
|
// and we haven't yet begun to construct a selector, then
|
|
// we can be sure that this is not a message dispatch, and
|
|
// we should simply return so that the lexer can continue
|
|
// to preprocess normally.
|
|
if(token == TOKEN_COMMA && !selector.length)
|
|
array = true;//return literal + token;
|
|
// A colon (':') alerts us that we've reached the end of a label.
|
|
if(token == TOKEN_COLON)
|
|
{
|
|
// If the previous token was actually white space, so this
|
|
// is an empty label.
|
|
var last = tokens.last();
|
|
|
|
if (last && (!(/\S/).test(last) || last.substr(0, 2) == "//" || last.substr(0, 2) == "/*"))
|
|
{
|
|
selector += ':';
|
|
marg_list[marg_list.length - 1] += previous;
|
|
marg_list[marg_list.length] = previous = "";
|
|
}
|
|
|
|
// If not the previous token was was part of the label and
|
|
// thus should be appended to the selector.
|
|
else
|
|
{
|
|
selector += previous + ":";
|
|
marg_list[marg_list.length] = previous = "";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Generally we are unconcerned with whitespace, however the new
|
|
// operator requires it.
|
|
if (previous == TOKEN_NEW)
|
|
previous = "new ";
|
|
|
|
// If we've already begun building our selector, then the token
|
|
// should be applied to the current argument.
|
|
if (selector.length)
|
|
marg_list[marg_list.length - 1] += previous;
|
|
|
|
// If not, then it belongs to the receiver.
|
|
else
|
|
receiver += previous;
|
|
|
|
previous = token.toString();
|
|
}
|
|
}
|
|
// If not, add it to previous to be interpreted as one block.
|
|
else
|
|
{
|
|
// We know this colon (':') to match a previous tertiary expression.
|
|
// FIXME PARSER_BUG: This does not properly account for interspersed { } and ?:
|
|
// https://trac.280north.com/ticket/15
|
|
if(token == TOKEN_COLON && !braces)
|
|
--tertiary;
|
|
|
|
previous += token;
|
|
}
|
|
|
|
// The literal interpretation is always handled in the same
|
|
// manner, simply aggregate the tokens, unless we have a new token,
|
|
// in which case we need to add back the whitespace we removed.
|
|
if (token == TOKEN_NEW)
|
|
CONCAT(literal, "new ");
|
|
else
|
|
CONCAT(literal, token);
|
|
}
|
|
|
|
// If we have a selector, then add the remaining string to the argument.
|
|
if (selector.length)
|
|
marg_list[marg_list.length - 1] += previous;
|
|
|
|
// If not, check whether the final character of our proposed receiver
|
|
// is an operator or the new keyword. Also check that the previous
|
|
// expression does not begin with a parenthesis, since this means it
|
|
// definitely can't be a selector.
|
|
// FIXME: There is probably a more concise and efficient way to represent
|
|
// these regular expressions.
|
|
// FIXME: Should the second expression be the same as the first. For example,
|
|
// array[i%] becomes arrayobjj_msgSend(i,"%") if not. This is an error either
|
|
// way though. https://trac.280north.com/ticket/6
|
|
else if(!array && receiver.length && !((/[\:\+\-\*\/\=\<\>\&\|\!\.\%]/).test(receiver.charAt(receiver.length - 1))) &&
|
|
receiver != TOKEN_NEW && !(/[\+\-\*\/\=\<\>\&\|\!\.\[\^\(]/).test(previous.charAt(0)))
|
|
selector = previous;
|
|
|
|
// If we did not build a selector through the parsing process, then we
|
|
// are either a single entry literal array, or an array index, and so
|
|
// we should simply return our literal string.
|
|
else
|
|
{
|
|
CONCAT(buffer, literal);
|
|
CONCAT(buffer, ']');
|
|
|
|
// Return no matter what!
|
|
if (!aStringBuffer)
|
|
return buffer;
|
|
else
|
|
return;
|
|
}
|
|
|
|
// NOTE: For now, turn this behavior off.
|
|
// Classes act much like keywords since they are not directly manipulatable.
|
|
// We thus check whether it exists in our class hash or working hash, and if
|
|
// instead use a reference to it if it does.
|
|
// if (tokens.containsClass(receiver) || objj_getClass(receiver)) receiver = "objj_getClass(\"" + receiver + "\")";
|
|
|
|
// The first two arguments are always the receiver and the selector.
|
|
CONCAT(buffer, msgSend);
|
|
CONCAT(buffer, '(' + receiver + ", \"" + sel_getUid(selector) + "\"");
|
|
|
|
// Populate the remaining parameters with the provided arguments.
|
|
var index = 0,
|
|
count = marg_list.length;
|
|
|
|
for(; index < count; ++index)
|
|
CONCAT(buffer, ", " + marg_list[index]);
|
|
|
|
// Return the fully preprocessed message dispatch.
|
|
CONCAT(buffer, ')');
|
|
|
|
if (!aStringBuffer)
|
|
return buffer;
|
|
}
|
|
|
|
|
|
objj_preprocessor.prototype.directive = function(tokens, aStringBuffer, allowedDirectivesFlags)
|
|
{
|
|
// Grab the next token, preprocessor directives follow '@' immediately.
|
|
var buffer = aStringBuffer ? aStringBuffer : new objj_stringBuffer(),
|
|
token = tokens.next();
|
|
|
|
// To provide compatibility with Objective-C files, we convert NSString literals into
|
|
// toll-freed JavaScript/CPString strings.
|
|
if (token.charAt(0) == TOKEN_DOUBLE_QUOTE)
|
|
CONCAT(buffer, token);
|
|
|
|
// Currently we simply swallow forward declarations and only provide them to allow
|
|
// compatibility with Objective-C files.
|
|
else if (token == TOKEN_CLASS)
|
|
{
|
|
tokens.skip_whitespace();
|
|
|
|
return;
|
|
}
|
|
|
|
// @implementation Class implementations
|
|
else if (token == TOKEN_IMPLEMENTATION)
|
|
this.implementation(tokens, buffer);
|
|
|
|
// @import
|
|
else if (token == TOKEN_IMPORT)
|
|
this._import(tokens);
|
|
|
|
// @selector
|
|
else if (token == TOKEN_SELECTOR)
|
|
this.selector(tokens, buffer);
|
|
|
|
else if (token == TOKEN_ACCESSORS)
|
|
return this.accessors(tokens);
|
|
|
|
if (!aStringBuffer)
|
|
return buffer;
|
|
}
|
|
|
|
objj_preprocessor.prototype.fragment = function()
|
|
{
|
|
var preprocessed = this._preprocessed.toString();
|
|
|
|
// But make sure it's not just all whitespace!
|
|
if ((/[^\s]/).test(preprocessed))
|
|
this._fragments.push(fragment_create_code(preprocessed, this._bundle, this._file));
|
|
|
|
this._preprocessed.clear();
|
|
}
|
|
|
|
objj_preprocessor.prototype.implementation = function(tokens, /*objj_stringBuffer*/ aStringBuffer)
|
|
{
|
|
var buffer = aStringBuffer,
|
|
token = "",
|
|
category = NO,
|
|
class_name = tokens.skip_whitespace(),
|
|
superclass_name = "Nil",
|
|
|
|
instance_methods = new objj_stringBuffer(),
|
|
class_methods = new objj_stringBuffer();
|
|
|
|
if (!(/^\w/).test(class_name))
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Expected class name, found \"" + class_name + "\"."));
|
|
|
|
this._currentSuperClass = NULL;
|
|
this._currentClass = class_name;
|
|
|
|
// If we reach an open parenthesis, we are declaring a category.
|
|
if((token = tokens.skip_whitespace()) == TOKEN_OPEN_PARENTHESIS)
|
|
{
|
|
token = tokens.skip_whitespace();
|
|
|
|
if (token == TOKEN_CLOSE_PARENTHESIS)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Can't Have Empty Category Name for class \"" + class_name + "\"."));
|
|
|
|
if (tokens.skip_whitespace() != TOKEN_CLOSE_PARENTHESIS)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Improper Category Definition for class \"" + class_name + "\"."));
|
|
|
|
CONCAT(buffer, "{\nvar the_class = objj_getClass(\"" + class_name + "\")\n");
|
|
CONCAT(buffer, "if(!the_class) objj_exception_throw(new objj_exception(OBJJClassNotFoundException, \"*** Could not find definition for class \\\"" + class_name + "\\\"\"));\n");
|
|
CONCAT(buffer, "var meta_class = the_class.isa;");
|
|
|
|
var superclass_name = dictionary_getValue(SUPER_CLASSES, class_name);
|
|
|
|
// FIXME: We should have a better solution for this case, although it's actually not much slower than the real case.
|
|
if (!superclass_name)
|
|
this._currentSuperClass = "objj_getClass(\"" + class_name + "\").super_class";
|
|
else
|
|
this._currentSuperClass = "objj_getClass(\"" + superclass_name + "\")";
|
|
}
|
|
else
|
|
{
|
|
// If we reach a colon (':'), then a superclass is being declared.
|
|
if(token == TOKEN_COLON)
|
|
{
|
|
token = tokens.skip_whitespace();
|
|
|
|
if (!(/^[a-zA-Z_$](\w|$)*$/).test(token))
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Expected class name, found \"" + token + "\"."));
|
|
|
|
superclass_name = token;
|
|
this._currentSuperClass = "objj_getClass(\"" + superclass_name + "\")";
|
|
|
|
dictionary_setValue(SUPER_CLASSES, class_name, superclass_name);
|
|
|
|
token = tokens.skip_whitespace();
|
|
}
|
|
|
|
CONCAT(buffer, "{var the_class = objj_allocateClassPair(" + superclass_name + ", \"" + class_name + "\"),\nmeta_class = the_class.isa;");
|
|
|
|
// If we are at an opening curly brace ('{'), then we have an ivar declaration.
|
|
if (token == TOKEN_OPEN_BRACE)
|
|
{
|
|
var ivar_count = 0,
|
|
declaration = [],
|
|
|
|
attributes,
|
|
accessors = {};
|
|
|
|
while((token = tokens.skip_whitespace()) && token != TOKEN_CLOSE_BRACE)
|
|
{
|
|
if (token == TOKEN_PREPROCESSOR)
|
|
attributes = this.directive(tokens);
|
|
|
|
else if (token == TOKEN_SEMICOLON)
|
|
{
|
|
if (ivar_count++ == 0)
|
|
CONCAT(buffer, "class_addIvars(the_class, [");
|
|
else
|
|
CONCAT(buffer, ", ");
|
|
|
|
var name = declaration[declaration.length - 1];
|
|
|
|
CONCAT(buffer, "new objj_ivar(\"" + name + "\")");
|
|
|
|
declaration = [];
|
|
|
|
if (attributes)
|
|
{
|
|
accessors[name] = attributes;
|
|
attributes = NULL;
|
|
}
|
|
}
|
|
else
|
|
declaration.push(token);
|
|
}
|
|
|
|
// If we have objects in our declaration, the user forgot a ';'.
|
|
if (declaration.length)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Expected ';' in ivar declaration, found '}'."));
|
|
|
|
if (ivar_count)
|
|
CONCAT(buffer, "]);\n");
|
|
|
|
if (!token)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Expected '}'"));
|
|
|
|
for (ivar_name in accessors)
|
|
{
|
|
var accessor = accessors[ivar_name],
|
|
property = accessor["property"] || ivar_name;
|
|
|
|
// getter
|
|
var getterName = accessor["getter"] || property,
|
|
getterCode = "(id)" + getterName + "\n{\nreturn " + ivar_name + ";\n}";
|
|
|
|
if (IS_NOT_EMPTY(instance_methods))
|
|
CONCAT(instance_methods, ",\n");
|
|
|
|
CONCAT(instance_methods, this.method(new objj_lexer(getterCode)));
|
|
|
|
// setter
|
|
if (accessor["readonly"])
|
|
continue;
|
|
|
|
var setterName = accessor["setter"];
|
|
|
|
if (!setterName)
|
|
{
|
|
var start = property.charAt(0) == '_' ? 1 : 0;
|
|
setterName = (start ? "_" : "") + "set" + property.substr(start, 1).toUpperCase() + property.substring(start + 1) + ":";
|
|
}
|
|
|
|
var setterCode = "(void)" + setterName + "(id)newValue\n{\n";
|
|
|
|
if (accessor["copy"])
|
|
setterCode += "if (" + ivar_name + " !== newValue)\n" + ivar_name + " = [newValue copy];\n}";
|
|
else
|
|
setterCode += ivar_name + " = newValue;\n}";
|
|
|
|
if (IS_NOT_EMPTY(instance_methods))
|
|
CONCAT(instance_methods, ",\n");
|
|
|
|
CONCAT(instance_methods, this.method(new objj_lexer(setterCode)));
|
|
}
|
|
}
|
|
else
|
|
tokens.previous();
|
|
|
|
// We must make a new class object for our class definition.
|
|
CONCAT(buffer, "objj_registerClassPair(the_class);\n");
|
|
|
|
// Add this class to the current bundle.
|
|
CONCAT(buffer, "objj_addClassForBundle(the_class, objj_getBundleWithPath(OBJJ_CURRENT_BUNDLE.path));\n");
|
|
}
|
|
|
|
while ((token = tokens.skip_whitespace()))
|
|
{
|
|
if (token == TOKEN_PLUS)
|
|
{
|
|
if (IS_NOT_EMPTY(class_methods))
|
|
CONCAT(class_methods, ", ");
|
|
|
|
CONCAT(class_methods, this.method(tokens));
|
|
}
|
|
|
|
else if (token == TOKEN_MINUS)
|
|
{
|
|
if (IS_NOT_EMPTY(instance_methods))
|
|
CONCAT(instance_methods, ", ");
|
|
|
|
CONCAT(instance_methods, this.method(tokens));
|
|
}
|
|
|
|
// Check if we've reached @end...
|
|
else if (token == TOKEN_PREPROCESSOR)
|
|
{
|
|
// The only preprocessor directive we should ever encounter at this point is @end.
|
|
if ((token = tokens.next()) == TOKEN_END)
|
|
break;
|
|
|
|
else
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Expected \"@end\", found \"@" + token + "\"."));
|
|
}
|
|
}
|
|
|
|
if (IS_NOT_EMPTY(instance_methods))
|
|
{
|
|
CONCAT(buffer, "class_addMethods(the_class, [");
|
|
CONCAT(buffer, instance_methods);
|
|
CONCAT(buffer, "]);\n");
|
|
}
|
|
|
|
if (IS_NOT_EMPTY(class_methods))
|
|
{
|
|
CONCAT(buffer, "class_addMethods(meta_class, [");
|
|
CONCAT(buffer, class_methods);
|
|
CONCAT(buffer, "]);\n");
|
|
}
|
|
|
|
CONCAT(buffer, '}');
|
|
}
|
|
|
|
objj_preprocessor.prototype._import = function(tokens)
|
|
{
|
|
// The introduction of an import statement forces the creation of a code fragment.
|
|
this.fragment();
|
|
|
|
var path = "",
|
|
token = tokens.skip_whitespace(),
|
|
isLocal = (token != TOKEN_LESS_THAN);
|
|
|
|
if (token == TOKEN_LESS_THAN)
|
|
{
|
|
while((token = tokens.next()) && token != TOKEN_GREATER_THAN)
|
|
path += token;
|
|
|
|
if(!token)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Unterminated import statement."));
|
|
}
|
|
|
|
else if (token.charAt(0) == TOKEN_DOUBLE_QUOTE)
|
|
path = token.substr(1, token.length - 2);
|
|
|
|
else
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Expecting '<' or '\"', found \"" + token + "\"."));
|
|
|
|
this._fragments.push(fragment_create_file(path, NULL, isLocal, this._file));
|
|
}
|
|
|
|
objj_preprocessor.prototype.method = function(tokens)
|
|
{
|
|
var buffer = new objj_stringBuffer(),
|
|
token,
|
|
selector = "",
|
|
parameters = [];
|
|
|
|
while((token = tokens.skip_whitespace()) && token != TOKEN_OPEN_BRACE)
|
|
{
|
|
if (token == TOKEN_COLON)
|
|
{
|
|
// Colons are part of the selector name
|
|
selector += token;
|
|
|
|
token = tokens.skip_whitespace();
|
|
|
|
if (token == TOKEN_OPEN_PARENTHESIS)
|
|
{
|
|
// Swallow parameter/return type. Perhaps later we can use this for debugging?
|
|
while((token = tokens.skip_whitespace()) && token != TOKEN_CLOSE_PARENTHESIS) ;
|
|
|
|
token = tokens.skip_whitespace();
|
|
}
|
|
|
|
// Since this follows a colon, this must be the parameter name.
|
|
parameters[parameters.length] = token;
|
|
}
|
|
|
|
else if (token == TOKEN_OPEN_PARENTHESIS)
|
|
// Since :( is handled above, this must be the return type, just swallow it.
|
|
while((token = tokens.skip_whitespace()) && token != TOKEN_CLOSE_PARENTHESIS) ;
|
|
|
|
// Argument list ", ..."
|
|
else if (token == TOKEN_COMMA)
|
|
{
|
|
// At this point, "..." MUST follow.
|
|
if ((token = tokens.skip_whitespace()) != TOKEN_PERIOD || tokens.next() != TOKEN_PERIOD || tokens.next() != TOKEN_PERIOD)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Argument list expected after ','."));
|
|
|
|
// FIXME: Shouldn't allow any more after this.
|
|
}
|
|
|
|
// Build selector name.
|
|
else
|
|
selector += token;
|
|
}
|
|
|
|
var index = 0,
|
|
count = parameters.length;
|
|
|
|
CONCAT(buffer, "new objj_method(sel_getUid(\"");
|
|
CONCAT(buffer, selector);
|
|
CONCAT(buffer, "\"), function");
|
|
|
|
if (this._flags & OBJJ_PREPROCESSOR_DEBUG_SYMBOLS)
|
|
CONCAT(buffer, " $" + this._currentClass + "__" + selector.replace(/:/g, "_"));
|
|
|
|
CONCAT(buffer, "(self, _cmd");
|
|
|
|
for(; index < count; ++index)
|
|
{
|
|
CONCAT(buffer, ", ");
|
|
CONCAT(buffer, parameters[index]);
|
|
}
|
|
|
|
CONCAT(buffer, ")\n{ with(self)\n{");
|
|
CONCAT(buffer, this.preprocess(tokens, NULL, TOKEN_CLOSE_BRACE, TOKEN_OPEN_BRACE));
|
|
CONCAT(buffer, "}\n})");
|
|
|
|
return buffer;
|
|
}
|
|
|
|
objj_preprocessor.prototype.preprocess = function(tokens, /*objj_stringBuffer*/ aStringBuffer, terminator, instigator)
|
|
{
|
|
var buffer = aStringBuffer ? aStringBuffer : new objj_stringBuffer(),
|
|
count = 0,
|
|
token = "";
|
|
|
|
while((token = tokens.next()) && ((token != terminator) || count))
|
|
{
|
|
if (instigator)
|
|
{
|
|
if (token == instigator)
|
|
++count;
|
|
|
|
else if (token == terminator)
|
|
--count;
|
|
}
|
|
|
|
// We convert import statements into objj_request_import function calls, converting
|
|
// the the file paths to use the proper search arguments.
|
|
if(token == TOKEN_IMPORT)
|
|
{
|
|
objj_fprintf(warning_stream, "import keyword is deprecated, use @import instead.");
|
|
|
|
this._import(tokens);
|
|
}
|
|
// Safari can't handle function declarations of the form function [name]([arguments]) { }
|
|
// in evals. It requires them to be in the form [name] = function([arguments]) { }. So we
|
|
// need to find these and fix them.
|
|
else if(token == TOKEN_FUNCTION)
|
|
{//if (window.p) alert("function");
|
|
var accumulator = "";
|
|
|
|
// Following the function identifier we can either have an open parenthesis or an identifier:
|
|
while((token = tokens.next()) && token != TOKEN_OPEN_PARENTHESIS && !(/^\w/).test(token))
|
|
accumulator += token;
|
|
|
|
// If the next token is an open parenthesis, we have a standard function and we don't have to
|
|
// change it:
|
|
if(token == TOKEN_OPEN_PARENTHESIS)
|
|
CONCAT(buffer, "function" + accumulator + '(');
|
|
|
|
// If it's not a parenthesis, we know we have a non-supported function declaration, so fix it:
|
|
else
|
|
{
|
|
CONCAT(buffer, token + "= function");
|
|
|
|
#if FIREBUG
|
|
var functionName = token;
|
|
|
|
// Skip everything until the next close parenthesis.
|
|
while((token = tokens.next()) && token != TOKEN_CLOSE_PARENTHESIS)
|
|
CONCAT(buffer, token);
|
|
|
|
// Don't forget the last token!
|
|
CONCAT(buffer, token);
|
|
|
|
// Skip everything until the next open curly brace.
|
|
while((token = tokens.next()) && token != TOKEN_OPEN_BRACE)
|
|
CONCAT(buffer, token);
|
|
|
|
// Place the open curly brace as well, and the function name
|
|
CONCAT(buffer, token + "\n \"__FIREBUG_FNAME__" + functionName + "\".length;\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// If we reach an @ symbol, we are at a preprocessor directive.
|
|
else if (token == TOKEN_PREPROCESSOR)
|
|
this.directive(tokens, buffer);
|
|
|
|
// If we reach a bracket, we will either be preprocessing a message send, a literal
|
|
// array, or an array index.
|
|
else if (token == TOKEN_OPEN_BRACKET)
|
|
this.brackets(tokens, buffer);
|
|
|
|
// If not simply append the token.
|
|
else
|
|
CONCAT(buffer, token);
|
|
}
|
|
|
|
if (!aStringBuffer)
|
|
return buffer;
|
|
}
|
|
|
|
objj_preprocessor.prototype.selector = function(tokens, aStringBuffer)
|
|
{
|
|
var buffer = aStringBuffer ? aStringBuffer : new objj_stringBuffer();
|
|
|
|
CONCAT(buffer, "sel_getUid(\"");
|
|
|
|
// Swallow open parenthesis.
|
|
if (tokens.skip_whitespace() != TOKEN_OPEN_PARENTHESIS)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Expected ')'"));
|
|
|
|
// Eat leading whitespace
|
|
var selector = tokens.skip_whitespace();
|
|
|
|
if (selector == TOKEN_CLOSE_PARENTHESIS)
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Unexpected ')', can't have empty @selector()"));
|
|
|
|
CONCAT(aStringBuffer, selector);
|
|
|
|
var token,
|
|
starting = true;
|
|
|
|
while ((token = tokens.next()) && token != TOKEN_CLOSE_PARENTHESIS)
|
|
{
|
|
if (starting && /^\d+$/.test(token) || !(/^(\w|$|\:)/.test(token)))
|
|
{
|
|
// Only allow tail whitespace
|
|
if (!(/\S/).test(token))
|
|
if (tokens.skip_whitespace() == TOKEN_CLOSE_PARENTHESIS)
|
|
break;
|
|
else
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Unexpected whitespace in @selector()."));
|
|
else
|
|
objj_exception_throw(new objj_exception(OBJJParseException, "*** Illegal character '" + token + "' in @selector()."));
|
|
}
|
|
|
|
CONCAT(buffer, token);
|
|
starting = (token == TOKEN_COLON);
|
|
}
|
|
|
|
CONCAT(buffer, "\")");
|
|
|
|
if (!aStringBuffer)
|
|
return buffer;
|
|
}
|