#include "lobscript.h"
QMap<QString,QString> helpData; // will store all Lob and LobAddress help data globally
void fillHelpDataIfNeeded () // this function fills that map just once per run of Lurch
{
if ( helpData.isEmpty() ) {
const int returnType = 0;
const int signature = 1;
const int doxLink = 2;
const int type = 3;
const int description = 4;
const int codeLink = 5;
QStringList lines;
QMap<QString,QStringList> tmp;
// load into tmp all the data from the doxygen tag file (incl. URLs to doxygen docs)
QFile f1( ":/doxygen/Lurch-doxygen.tag.processed" );
if ( f1.open( QIODevice::ReadOnly ) )
lines = QTextStream( &f1 ).readAll().split( "\n" );
foreach ( QString line, lines ) {
QStringList bits = line.split( " // " );
for ( int i = 0 ; i < bits.count() ; i++ ) bits[i] = bits[i].trimmed();
QString key = bits.takeFirst();
while ( bits.count() < 3 ) bits << "";
while ( bits.count() > 3 ) bits.takeLast();
tmp[key] = bits;
}
// add into tmp all the data from the doxygen xml output (incl. brief descriptions)
QFile f2( ":/doxygen/Lurch-doxygen.xml.processed" );
if ( f2.open( QIODevice::ReadOnly ) )
lines = QTextStream( &f2 ).readAll().split( "\n" );
foreach ( QString line, lines ) {
QStringList bits = line.split( " // " );
for ( int i = 0 ; i < bits.count() ; i++ ) bits[i] = bits[i].trimmed();
QString key = bits.takeFirst();
if ( tmp.contains( key ) ) {
tmp[key] << bits;
} else {
tmp[key] = bits;
tmp[key].prepend( "" );
tmp[key].prepend( "" );
tmp[key].prepend( "" );
}
}
// use all this to build nice help HTML for each Lob/LobAddress member
QRegExp scriptPrefix( "\\bscript_(\\w+)" );
foreach ( QString key, tmp.keys() ) {
QStringList nameParts = key.split( "::" );
QStringList data = tmp[key];
while ( data.count() <= codeLink ) data << "";
QString clink = data[codeLink];
int num = clink.indexOf( "#" );
int lastSlash = clink.lastIndexOf( "/" );
clink = QString( "%1-source.html#l%2" )
.arg( clink.mid( lastSlash + 1, num - lastSlash - 1 )
.replace( ".", "_8" ) )
.arg( clink.mid( num + 1 ), 5, '0' );
data[description].replace( scriptPrefix, "\\1" );
helpData[key] = QString( "<b>%1</b> is a %2 in the %3 class.<br>"
"<i>%4</i><br>"
"<a href=\"http://lurch.sourceforge.net/doxygen/html/"
"%5\">read more online</a><br>"
"<a href=\"http://lurch.sourceforge.net/doxygen/html/"
"%6\">view C++ source online</a> (<tt>%7 %8 %9</tt>)" )
.arg( nameParts.last() ).arg( data[type] ).arg( nameParts.first() )
.arg( data[description] ).arg( data[doxLink] ).arg( clink )
.arg( data[returnType] ).arg( key ).arg( data[signature] );
}
}
}
QString getHelpDataFor ( QString className, QString memberName )
{
return helpData[QString( "%1::%2" ).arg( className ).arg( memberName )];
}
void putHelpDataInto ( QScriptValue object, QString className )
{
fillHelpDataIfNeeded();
foreach ( QString key, helpData.keys() ) {
if ( !key.startsWith( className + "::" ) )
continue;
QScriptValue member = object.property( key.mid( className.length() + 2 ) );
if ( member.isValid() && member.isObject() )
member.setProperty( "helpHTML", helpData[key] );
}
}
QString getContext ( QString code, int lineNumber, bool includeNumbers )
{
QStringList lines = code.split( "\n" );
QString result;
// we treat lineNumber as if it were lineNumber-1, because of 0-based vs. 1-based indexing
for ( int i = lineNumber - 3 ; i <= lineNumber + 1 ; i++ ) {
if ( ( i >= 0 ) && ( i < lines.count() ) ) {
if ( includeNumbers )
result += QString( "%1: " ).arg( i + 1, 5 ); // again, reconciling indexing
result += lines[i] + "\n";
}
}
return result.isEmpty() ? QString( "INVALID LINE" ) : result;
}
QScriptValue LobStarToScriptValue ( QScriptEngine* engine, Lob* const& in )
{
QScriptValue result = engine->newQObject( in, QScriptEngine::ScriptOwnership );
result.setPrototype( engine->defaultPrototype( qMetaTypeId<Lob>() ) );
// putHelpDataInto( result, "Lob" );
return result;
}
void LobStarFromScriptValue ( const QScriptValue& object, Lob*& out )
{
out = qobject_cast<Lob*>( object.toQObject() );
}
QScriptValue LobToScriptValue ( QScriptEngine* engine, const Lob& in )
{
return engine->toScriptValue( new Lob( in ) );
}
void LobFromScriptValue ( const QScriptValue& object, Lob& out )
{
Lob* ptr = qobject_cast<Lob*>( object.toQObject() );
out = ptr ? *ptr : Lob();
}
QScriptValue LobEquals ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue(
engine->fromScriptValue<Lob>( context->thisObject() )
== engine->fromScriptValue<Lob>( context->argument( 0 ) ) );
}
QScriptValue LobAddressToScriptValue ( QScriptEngine* engine, const LobAddress& in )
{
QScriptValue result = engine->newObject();
QScriptValue T = engine->newArray( in.numSteps() );
QScriptValue I = engine->newArray( in.numSteps() );
for ( unsigned int i = 0 ; i < in.numSteps() ; i++ ) {
T.setProperty( i, QScriptValue( engine, in.stepType( i ) ) );
I.setProperty( i, QScriptValue( engine, in.stepIndex( i ) ) );
}
result.setProperty( "stepTypes", T );
result.setProperty( "stepIndices", I );
result.setPrototype( engine->globalObject().property( "LobAddress" ) );
// putHelpDataInto( result, "LobAddress" );
return result;
}
void LobAddressFromScriptValue ( const QScriptValue& object, LobAddress& out )
{
out = LobAddress();
QScriptValue T = object.property( "stepTypes" );
QScriptValue I = object.property( "stepIndices" );
int n = (int)( T.property( "length" ).toInteger() );
for ( int i = 0 ; i < n ; i++ )
out.addStep( (unsigned int)I.property( i ).toInteger(),
(OmNode::OwnershipType)T.property( i ).toInteger() );
}
QScriptValue LobAddressBuilder ( QScriptContext* context, QScriptEngine* engine )
{
return LobAddressToScriptValue( engine, LobAddress(
context->argument( 0 ).toString() ) );
}
QScriptValue LobAddressToString ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue(
engine->fromScriptValue<LobAddress>( context->thisObject() ).toString() );
}
QScriptValue LobAddressNumSteps ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue(
engine->fromScriptValue<LobAddress>( context->thisObject() ).numSteps() );
}
QScriptValue LobAddressEquals ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue(
engine->fromScriptValue<LobAddress>( context->thisObject() )
== engine->fromScriptValue<LobAddress>( context->argument( 0 ) ) );
}
QScriptValue LobAddressLessThan ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue(
engine->fromScriptValue<LobAddress>( context->thisObject() )
< engine->fromScriptValue<LobAddress>( context->argument( 0 ) ) );
}
QScriptValue LobScriptConstructor ( QScriptContext* context, QScriptEngine* engine )
{
if ( context->argumentCount() > 1 )
return context->throwError( "Too many arguments to Lob constructor" );
QScriptValue object;
Lob* result = new Lob;
if ( context->isCalledAsConstructor() ) {
// they've already built it for us, so start by promoting that to a QObject w/new Lob:
object = engine->newQObject( context->thisObject(), result,
QScriptEngine::ScriptOwnership );
} else {
// not called as "new Lob()", just "Lob()", so create our own new Lob:
object = engine->newQObject( result, QScriptEngine::ScriptOwnership );
object.setPrototype( engine->defaultPrototype( qMetaTypeId<Lob>() ) );
}
if ( context->argumentCount() == 1 ) {
QScriptValue arg = context->argument( 0 );
if ( !arg.isString() )
return context->throwError( "Lob constructor requires a string parameter" );
Lob buildMe;
try {
buildMe = Lob::fromXML( arg.toString() );
if ( buildMe.numChildren() == 1 ) {
Lob tmp = buildMe;
buildMe = buildMe.child();
tmp.removeChild();
}
} catch ( OmException& e ) {
return context->throwError( QString( "When parsing Lob XML: %1" )
.arg( e.translate() ) );
} catch ( ... ) {
return context->throwError( "Unknown error when parsing Lob XML" );
}
*result = buildMe;
}
// If it's a constructor, return an undefined object to say that
// thisObject() is the real result of new Lob(...); otherwise, return the new Lob.
// putHelpDataInto( object, "Lob" );
return context->isCalledAsConstructor() ? engine->undefinedValue() : object;
}
QScriptValue exclusionsConvenienceFunction ( QScriptContext* context, QScriptEngine* engine )
{
QStringList result;
for ( int i = 0 ; i < context->argumentCount() ; i++ ) {
QScriptValue v = context->argument( i );
Lob L = engine->fromScriptValue<Lob>( v );
if ( L.nodeType() == OmSymbolType ) {
QStringList pair = L.toVariant().toStringList();
result << pair;
}
}
return engine->toScriptValue( result );
}
QScriptValue scriptEscapeXML ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue( escapeXML( context->argument( 0 ).toString() ) );
}
QScriptValue scriptUnescapeXML ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue( unescapeXML( context->argument( 0 ).toString() ) );
}
QString escapedChar ( QChar c )
{
static const QChar cA = QChar::fromLatin1( 'A' );
static const QChar cZ = QChar::fromLatin1( 'Z' );
static const QChar ca = QChar::fromLatin1( 'a' );
static const QChar cz = QChar::fromLatin1( 'z' );
static const QChar c0 = QChar::fromLatin1( '0' );
static const QChar c9 = QChar::fromLatin1( '9' );
if ( ( c.row() == cA.row() ) && ( c.cell() >= cA.cell() ) && ( c.cell() <= cZ.cell() ) )
return c;
if ( ( c.row() == ca.row() ) && ( c.cell() >= ca.cell() ) && ( c.cell() <= cz.cell() ) )
return c;
if ( ( c.row() == c0.row() ) && ( c.cell() >= c0.cell() ) && ( c.cell() <= c9.cell() ) )
return c;
return QString( "_%1%2" ).arg( c.row(), 2, 16, c0 ).arg( c.cell(), 2, 16, c0 );
}
QString unescapedChar ( QString code )
{
if ( ( code.length() != 5 ) || ( code[0] != QChar::fromLatin1( '_' ) ) )
return code;
bool ok;
int row = code.mid( 1, 2 ).toInt( &ok, 16 );
if ( !ok )
return code;
int cell = code.mid( 3, 2 ).toInt( &ok, 16 );
if ( !ok )
return code;
return QChar( cell, row );
}
QString escapeChars ( QString input )
{
QString output;
foreach ( QChar c, input )
output += escapedChar( c );
return output;
}
QString unescapeChars( QString input )
{
QChar underscore = QChar::fromLatin1( '_' );
QString output;
for ( int i = 0 ; i < input.count() ; i++ ) {
QChar c = input[i];
if ( c == underscore ) {
output += unescapedChar( input.mid( i, 5 ) );
i += 4;
} else {
output += c;
}
}
return output;
}
QScriptValue scriptEscapeChars ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue( escapeChars( context->argument( 0 ).toString() ) );
}
QScriptValue scriptUnescapeChars ( QScriptContext* context, QScriptEngine* engine )
{
return engine->toScriptValue( unescapeChars( context->argument( 0 ).toString() ) );
}
QString toJavascriptString ( QString text, bool withBreaks )
{
if ( !withBreaks )
return "'" + text.replace( "\\", "\\\\" ).replace( "\n", "\\n" ).replace( "'", "\\'" )
.replace( "\r", "" ) + "'";
const int wordWrapColumn = 70;
QString result;
QString piece;
foreach ( QChar c, text ) {
bool mustWrap = false;
if ( c == '\\' ) {
piece += "\\\\";
} else if ( c == '"' ) {
piece += "\\\"";
} else if ( c == '\r' ) {
// pass
} else if ( c == '\n' ) {
piece += "\\n";
mustWrap = true;
} else {
piece += c;
}
if ( ( piece.length() > wordWrapColumn ) || mustWrap ) {
result += ( result.isEmpty() ? " \"" : " + \"" ) + piece + "\"\n";
piece = QString();
}
}
if ( !piece.isEmpty() )
result += ( result.isEmpty() ? " \"" : " + \"" ) + piece + "\"\n";
return result;
}
QScriptValue scriptCodeString ( QScriptContext* context, QScriptEngine* engine )
{
return ( context->argumentCount() == 1 ) ?
engine->toScriptValue( toJavascriptString( context->argument( 0 ).toString() ) ) :
engine->toScriptValue( toJavascriptString( context->argument( 0 ).toString(),
context->argument( 1 ).toBool() ) );
}
QScriptValue scriptHelp ( QScriptContext* context, QScriptEngine* engine )
{
// first figure out what it is we're getting help on
QScriptValue subject = context->argument( 0 );
if ( context->argumentCount() > 1 ) {
QScriptValue walk = subject;
QString pname = context->argument( 1 ).toString();
while ( walk.isValid() && !walk.property( pname ).property( "helpHTML" ).isValid() )
walk = walk.prototype();
if ( !walk.isValid() ) {
walk = subject;
while ( walk.isValid() && !walk.property( pname ).property( "helpHTML" ).isValid() )
walk = walk.property( "prototype" );
}
if ( walk.isValid() )
subject = walk;
subject = subject.property( pname );
}
if ( context->argumentCount() == 0 )
subject = engine->evaluate( "help" ); // you get help on the help function itself
// store that object for later reference
QScriptValue helpCmd = engine->globalObject().property( "help" );
if ( !helpCmd.property( "history" ).isValid() )
helpCmd.setProperty( "history", engine->newArray() );
QScriptValue history = helpCmd.property( "history" );
int hindex = history.property( "length" ).toInteger();
history.setProperty( hindex, subject );
QString hstr = QString( "helpHistory(%1)" ).arg( hindex );
// now compute what the resulting help message is, based on the type of the thing
QString result;
if ( !subject.isValid() ) {
result = QString( "There is no such thing on which to get help." );
} else if ( subject.isBool() || subject.isBoolean() ) {
result = QString( "The JavaScript boolean value %1" )
.arg( subject.toBool() ? "true" : "false" );
} else if ( subject.isDate() ) {
result = QString( "A JavaScript date value, %1" )
.arg( subject.toDateTime().toString() );
} else if ( subject.isError() ) {
result = QString( "A JavaScript error, whose text is:<br><tt>%1</tt>" )
.arg( subject.toString() );
} else if ( subject.isFunction() ) {
QScriptValue help = subject.property( "helpHTML" );
QRegExp re( "\\s*function\\s*(?:[a-zA-Z_][a-zA-Z_0-9]*)?\\s*\\(\\s*\\)\\s*\\{"
"\\s*\\[native code\\]\\s*\\}\\s*" );
bool isNative = re.exactMatch( subject.toString() );
if ( help.isValid() ) {
result = help.toString() + "<br>";
if ( isNative )
result += "<font color=#999999>Because this is a built-in function, "
"its source code is not viewable within Lurch.</font>";
else
result += QString( "<a href='execute %2'>view the source code "
"for this function</a>" ).arg( hstr );
} else {
if ( isNative )
result = "A built-in Lurch function with no help text available.<br>"
"As Lurch grows, more and more built-in functions will have "
"help text provided. Our apologies for the temporary inconvenience.";
else
result = QString( "A JavaScript function with no help text available.<br>"
"<a href='execute %1'>view its source code</a>" ).arg( hstr );
}
} else if ( subject.isNull() ) {
result = "The special JavaScript value \"null\"";
} else if ( subject.isNumber() ) {
result = QString( "A JavaScript number, with value %1" ).arg( subject.toString() );
} else if ( subject.isRegExp() ) {
result = QString( "A JavaScript regular expression object with this pattern:<br>"
"<tt>%1</tt>" ).arg( subject.toString() );
} else if ( subject.isString() ) {
QString str = subject.toString();
int L = str.length();
result = QString( "A JavaScript string of length %1, %2\"%3\"<br>"
"<a href='SEARCH:%4'>search online help for this string</a>" )
.arg( L ).arg( ( L < 200 ) ? "" : "starting with " )
.arg( escapeXML( ( L < 200 ) ? str : str.left( 200 ) ) )
.arg( str.replace( ".", " " ) );
} else if ( subject.isUndefined() ) {
result = "The special JavaScript value \"undefined\"";
} else if ( !subject.isObject() ) {
result = QString( "The value %1, with type %2" ).arg( subject.toString() )
.arg( subject.toVariant().typeName() );
} else {
QScriptValue help = subject.property( "helpHTML" );
if ( help.isValid() ) {
result = QString( "%1<br><a href='execute %2'>show the object's structure</a>" )
.arg( help.toString() ).arg( hstr );
} else if ( subject.isArray() ) {
result = QString( "It is an array containing %1 items.<br>"
"Individual elemets of an array <tt>A</tt> "
"can be accessed with code like <tt>A[0]</tt>, <tt>A[1]</tt>, "
"<tt>A[2]</tt>, etc.<br>"
"<a href='execute %2'>show this array object</a>" )
.arg( subject.property( "length" ).toInteger() ).arg( hstr );
} else if ( subject.instanceOf( engine->evaluate( "Lob" ) ) ) {
Lob A = engine->fromScriptValue<Lob>( subject );
QString type = engine->evaluate( QString( "OmTypes[%1]" ).arg( A.nodeType() ) )
.toString();
result = QString( "This object is a Lob (Lurch Object) of type %1.<br>"
"<a href='execute %2'>view its OpenMath XML</a>" )
.arg( type ).arg( hstr );
QScriptValue doc = engine->evaluate( "document" );
if ( doc.isValid() ) {
doc = doc.call();
if ( doc.isValid() ) {
Lob D = engine->fromScriptValue<Lob>( doc );
if ( A == D )
result += "<br>This Lob is the current document.";
else if ( A.hasAsAncestor( D ) )
result += "<br>This Lob is in the current document.";
else
result += "<br>This Lob is not in the current document.";
}
}
if ( A.nodeType() == OmSymbolType ) {
QString cd = A.toVariant().toList()[1].toString();
result += QString( "<br>Every symbol has a content dictionary that "
"may indicate where it was defined. In this "
"case it is \"%1\" (<a href='SEARCH:%2'>search for %3 "
"in online documentation</a>)." )
.arg( cd ).arg( cd ).arg( cd );
}
} else if ( subject.property( "stepTypes" ).isValid()
&& subject.property( "stepIndices" ).isValid() ) {
result = "This object is a LobAddress, which you can use to index Lobs.<br>"
"See <a href='execute help(Lob.address)'><tt>help(Lob.address)</tt></a> "
"and <a href='execute help(Lob.index)'><tt>help(Lob.index)</tt></a> "
"for more information.";
result += QString( "<br><a href='execute %1'>show the object's structure</a>" )
.arg( hstr );
} else {
result = QString( "That object does not have help text available."
"<br><a href='execute %1'>show the object's structure</a>" )
.arg( hstr );
}
QStringList names;
while ( subject.isObject() ) {
QScriptValueIterator it( subject );
while ( it.hasNext() ) {
it.next();
QString name = it.name();
int pos = name.indexOf( "(" );
if ( pos > -1 )
name = name.left( pos );
if ( ( it.flags() & QScriptValue::SkipInEnumeration ) || ( name == "helpHTML" )
|| ( name.startsWith( "__" ) && name.endsWith( "__" ) ) )
continue;
if ( names.contains( name ) )
continue;
names << name;
}
subject = subject.prototype();
}
QMap <QString,QString> nameSorter;
QRegExp allDigits( "\\d+" );
for ( int i = 0 ; i < names.count() ; i++ ) {
QString key = allDigits.exactMatch( names[i] ) ?
QString( "0_%1" ).arg( names[i], 10 ) : ( "1_" + names[i].toLower() );
nameSorter[key] = QString( "<a href='execute help(%1,\"%2\")'>%3</a>" )
.arg( hstr ).arg( names[i] ).arg( names[i] );
}
QStringList sorted = nameSorter.values();
result += "<font size=-1><br>Properties: " + sorted.join( " " ) + "</font>";
}
// replace the special URL codes SEARCH: and DOX: with URLs
QRegExp search( "(['\"])SEARCH:(.*)\\1" );
search.setMinimal( true );
while ( true ) {
int pos = search.indexIn( result );
if ( pos == -1 )
break;
result = result.left( pos ) + search.cap( 1 )
+ "http://lurch.sourceforge.net/doxygen/html/search.php?query="
+ QString::fromLatin1( QUrl::toPercentEncoding( search.cap( 2 ) ) )
+ search.cap( 1 ) + result.mid( pos + search.matchedLength() );
}
QRegExp dox( "(['\"])DOX:(.*)\\1" );
dox.setMinimal( true );
while ( true ) {
int pos = dox.indexIn( result );
if ( pos == -1 )
break;
result = result.left( pos ) + dox.cap( 1 )
+ "http://lurch.sourceforge.net/doxygen/html/" + dox.cap( 2 )
+ dox.cap( 1 ) + result.mid( pos + dox.matchedLength() );
}
// print computed help message
engine->evaluate( "print" ).call( engine->nullValue(), QScriptValueList() << result );
return engine->undefinedValue();
}
QScriptValue scriptSetHelp ( QScriptContext* context, QScriptEngine* engine )
{
// require at least 2 arguments, the first of which is an object
if ( context->argumentCount() < 2 )
return context->throwError( "setHelp() requires at least two arguments" );
QScriptValue obj = context->argument( 0 );
if ( !obj.isObject() )
return context->throwError( "First argument to setHelp() must be an object" );
// get the help text
QString help = context->argument( 1 ).toString();
// if there is "see also" data, add it in
QStringList alsos;
for ( int i = 2 ; i < context->argumentCount() ; i++ ) {
QString searchFor = context->argument( i ).toString();
if ( searchFor.startsWith( "help:" ) ) {
QScriptValue alias = engine->globalObject().property( "__dependency_alias__" );
QString helpOn = searchFor.mid( 5 );
if ( alias.isValid() ) {
alsos << QString( "<a href='execute with ( %1 ) { help( %2 ) }'>%3</a>" )
.arg( alias.toString() ).arg( helpOn ).arg( helpOn );
} else {
alsos << QString( "<a href='execute help( %1 )'>%2</a>" )
.arg( helpOn ).arg( helpOn );
}
} else {
alsos << "<a href='SEARCH:" + searchFor + "'>" + searchFor + "</a>";
}
}
QString more;
if ( alsos.count() == 1 ) {
more = alsos[0];
} else if ( alsos.count() == 2 ) {
more = alsos.join( " and " );
} else if ( alsos.count() > 2 ) {
QString last = alsos.takeLast();
more = alsos.join( ", " ) + ", and " + last;
}
if ( !more.isEmpty() )
help += "<br>See " + more + " for more.";
// set the computed help text into the given object
obj.setProperty( "helpHTML", help );
return engine->toScriptValue( help );
}
QScriptValue scriptCopyHelp ( QScriptContext* context, QScriptEngine* /*engine*/ )
{
// require at least 2 arguments, both of which are objects
if ( context->argumentCount() < 2 )
return context->throwError( "copyHelp() requires at least two arguments" );
QScriptValue source = context->argument( 0 );
if ( !source.isObject() )
return context->throwError( "First argument to copyHelp() must be an object" );
QScriptValue dest = context->argument( 1 );
if ( !dest.isObject() )
return context->throwError( "Second argument to copyHelp() must be an object" );
// copy help property from one to the other
dest.setProperty( "helpHTML", source.property( "helpHTML" ) );
return dest.property( "helpHTML" );
}
QScriptValue scriptHelpHistory ( QScriptContext* context, QScriptEngine* engine )
{
if ( context->argumentCount() != 1 )
return context->throwError( "helpHistory() requires exactly one argument" );
QScriptValue index = context->argument( 0 );
if ( !index.isNumber() )
return context->throwError( "helpHistory() takes a number argument" );
int i = index.toInteger();
QScriptValue result = engine->globalObject().property( "help" ).property( "history" )
.property( i );
if ( !result.isValid() )
return context->throwError( "It seems that the script environment has been refreshed "
"since the help link you clicked on was generated, and "
"thus that link depends on data that no longer exists. "
"The script environment refreshes for several reasons, "
"usually when auto-run scripts in the document change." );
return result;
}
QScriptValue scriptSplitLurchURN ( QScriptContext* context, QScriptEngine* engine )
{
if ( context->argumentCount() != 1 )
return context->throwError( "splitLurchURN() takes exactly 1 argument only" );
return engine->toScriptValue( Lob::splitLurchURN( context->argument( 0 ).toString() ) );
}
QScriptValue scriptMakeLurchURN ( QScriptContext* context, QScriptEngine* engine )
{
if ( ( context->argumentCount() < 4 ) || ( context->argumentCount() > 5 ) )
return context->throwError( "makeLurchURN() takes 4 or 5 arguments only" );
QStringList bits;
for ( int i = 0 ; i < context->argumentCount() ; i++ )
bits << context->argument( i ).toString();
return engine->toScriptValue( Lob::makeLurchURN( bits[0], bits[1], bits[2], bits[3],
( bits.count() > 4 ) ? bits[4] : QString() ) );
}
void addLobsToScriptEngine ( QScriptEngine& engine )
{
// new script types
static int LobTypeID = 0;
static int LobStarTypeID = 0;
static int LobAddressTypeID = 0;
if ( !LobTypeID )
LobTypeID = qRegisterMetaType<Lob>( "Lob" );
if ( !LobStarTypeID )
LobStarTypeID = qRegisterMetaType<Lob*>( "Lob*" );
if ( !LobAddressTypeID )
LobAddressTypeID = qRegisterMetaType<LobAddress>( "LobAddress" );
qScriptRegisterMetaType( &engine, LobStarToScriptValue, LobStarFromScriptValue );
qScriptRegisterMetaType( &engine, LobToScriptValue, LobFromScriptValue );
qScriptRegisterMetaType( &engine, LobAddressToScriptValue, LobAddressFromScriptValue );
// OM types object
QScriptValue typesObject = engine.newObject();
QList<int> types;
types << OmIntegerType << OmBigIntegerType << OmFloatType << OmByteArrayType
<< OmVariableType << OmStringType << OmWStringType << OmSymbolType
<< OmPInstructionType << OmApplicationType << OmErrorType << OmObjectType
<< OmBindingType << OmUnknownType;
QStringList typeNames;
typeNames << "Integer" << "BigInteger" << "Float" << "ByteArray" << "Variable"
<< "String" << "WideString" << "Symbol" << "ProcessingInstruction"
<< "Application" << "Error" << "Object" << "Binding" << "Unknown";
for ( int i = 0 ; i < types.count() ; i++ ) {
typesObject.setProperty( typeNames[i], QScriptValue( &engine, types[i] ),
QScriptValue::ReadOnly | QScriptValue::Undeletable );
typesObject.setProperty( types[i], QScriptValue( &engine, typeNames[i] ),
QScriptValue::ReadOnly | QScriptValue::Undeletable );
}
typesObject.setProperty( "None", QScriptValue( &engine, OmNode::None ) );
typesObject.setProperty( "AsChild", QScriptValue( &engine, OmNode::AsChild ) );
typesObject.setProperty( "AsKey", QScriptValue( &engine, OmNode::AsKey ) );
typesObject.setProperty( "AsValue", QScriptValue( &engine, OmNode::AsValue ) );
engine.globalObject().setProperty( "OmTypes", typesObject );
typesObject.setProperty( "helpHTML",
"This object stores information about names and indices of "
"OpenMath node types.<br><a href='DOX:"
"lobscript_8h.html#f7a0b0e539c8627a2294fc0eb8f545e5'>read more "
"online here</a>" );
// Lob constructor
QScriptValue prototype = engine.newQObject( new Lob, QScriptEngine::ScriptOwnership );
prototype.setProperty( "equals", engine.newFunction( LobEquals ) );
prototype.property( "equals" ).setProperty( "helpHTML",
"Can tell whether two Lobs refer to the same OpenMath node in the same "
"OpenMath tree. This is different than comparing <tt>Lob1 == Lob2</tt>, "
"which compares whether the two JavaScript objects are equal; there may "
"be many JavaScript objects that refer to the same OpenMath node. It is "
"also different than comparing <tt>Lob1.equivalentTo( Lob2 )</tt>, which "
"is only a structural comparison, and does not ask if they are the same "
"node in the same tree." );
engine.setDefaultPrototype( qMetaTypeId<Lob>(), prototype );
QScriptValue constructor = engine.newFunction( LobScriptConstructor, prototype );
engine.globalObject().setProperty( "Lob", constructor );
putHelpDataInto( prototype, "Lob" );
constructor.setProperty( "helpHTML",
"The function for constructing Lobs (Lurch Objects).<br>"
"<a href='DOX:class_lob.html'>read more about Lobs online</a> "
"or <a href='execute Lob('<OMI>42</OMI>')"
" // press F1 to see help on this specific Lob'>"
"construct an example Lob</a>" );
// LobAddress prototype and builder
prototype = engine.newObject();
prototype.setProperty( "toString", engine.newFunction( LobAddressToString ) );
prototype.setProperty( "numSteps", engine.newFunction( LobAddressNumSteps ) );
prototype.setProperty( "equals", engine.newFunction( LobAddressEquals ) );
prototype.setProperty( "lessThan", engine.newFunction( LobAddressLessThan ) );
engine.globalObject().setProperty( "LobAddress", prototype );
QScriptValue afunc = engine.newFunction( LobAddressBuilder );
engine.globalObject().setProperty( "address", afunc );
putHelpDataInto( prototype, "LobAddress" );
afunc.setProperty( "helpHTML",
"The function for constructing LobAddresses.<br>"
"<a href='DOX:class_lob_address.html'>read more "
"about LobAddresses online</a>" );
// global functions for convenience
QScriptValue f;
f = engine.newFunction( exclusionsConvenienceFunction );
f.setProperty( "helpHTML",
"A convenience function useful when calling "
"<tt>someLob.equivalentTo( someOtherLob )</tt>.<br>"
"If you wish to pass the optional third argument to equivalentTo(), "
"a list of symbol Lobs (keys) to ignore when comparing attributes, "
"this function will format them correctly for you. Just pass any symbol "
"Lobs you want as the arguments.<br>"
"Example: <tt>exclude( sym1, sym2 )</tt> yields an array of strings "
"describing those two symbols, suitable as a third argumento to "
"equivalentTo(), as in <tt>myLob.equivalentTo( otherLob, true, "
"exclude( sym1, sym2 ) )</tt>." );
engine.globalObject().setProperty( "exclude", f );
f = engine.newFunction( scriptEscapeXML );
f.setProperty( "helpHTML",
"Simultaneously replaces ampersands, less than, greater than, quotes, and "
"apostrophes in the input with their corresponding XML entities in the "
"output. It also replaces the non-breaking space character (unicode 00A0) "
"with the HTML entity code &nbsp;, despite the fact that that's not XML, "
"but HTML." );
engine.globalObject().setProperty( "escapeXML", f );
f = engine.newFunction( scriptUnescapeXML );
f.setProperty( "helpHTML",
"Simultaneously replaces XHTML entity codes for ampersands, less than, greater "
"than, quotes, and apostrophes in the input with the characters that they "
"designate. It also replaces the non-breaking space code &nbsp; with the "
"corresponding unicode character 00A0, despite the fact that that's not "
"XML, but HTML." );
engine.globalObject().setProperty( "unescapeXML", f );
f = engine.newFunction( scriptEscapeChars );
f.setProperty( "helpHTML",
"Replaces any non-alphanumeric character in the input with a "
"five-character code in the output that uniquely identifies the replaced "
"character. That five-character code begins with an underscore and ends "
"with four digits, making the output of this function suitable for use as "
"an identifier in JavaScript, OpenMath, etc.<br>"
"Example: <tt>escapeChars( 'hello' )</tt> yields <tt>hello</tt>.<br>"
"Example: <tt>escapeChars( 'a b!c' )</tt> yields <tt>a_0020b_0021c</tt>." );
engine.globalObject().setProperty( "escapeChars", f );
f = engine.newFunction( scriptUnescapeChars );
f.setProperty( "helpHTML",
"Reverses the operation of escapeChars().<br>"
"<a href='execute help(escapeChars)'>See the documentation for "
"escapeChars()</a> for more information." );
engine.globalObject().setProperty( "unescapeChars", f );
f = engine.newFunction( scriptCodeString );
f.setProperty( "helpHTML",
"Wraps the given string in single quotes, and escapes any single quotes "
"and backslashes inside the string. This allows the result to be used "
"when building a string of JavaScript code.<br>"
"Example: <tt>eval( 'f(' + codeString( myStringData ) + ')' )</tt>" );
engine.globalObject().setProperty( "codeString", f );
f = engine.newFunction( scriptHelp );
f.setProperty( "helpHTML",
"Use <tt>help(x)</tt> for information on the value x.<br>"
"Use <tt>help(\"text\")</tt> to search online help for the given text.<br>"
"Examples: "
"<a href='execute help(document())'><tt>help(document())</tt></a> or "
"<a href='execute help(\"protocol\")'><tt>help(\"protocol\")</tt></a>.<br>"
"As a developer, to add help support to functions and objects you create, "
"see <a href='execute help(setHelp)'>setHelp()</a>.<br>"
"For a list of defined objects/functions, ask for "
"<a href='execute help(globalNamespace)'>"
"help on the global namespace</a>." );
engine.globalObject().setProperty( "help", f );
f = engine.newFunction( scriptSetHelp );
f.setProperty( "helpHTML",
"Call <tt>setHelp(X,\"help text\",optionalParameters...)</tt> to store in "
"X the given text as X's help data.<br>"
"X may be an object or function (including member functions, in an object "
"or in a prototype).<br>"
"The help text may contain HTML tags, and links will open in the user's "
"default browser. If a link is of the form "
"<tt><a href=\"execute ___\">...</a></tt> "
"then any code in place of the <tt>___</tt> will be executed when the "
"link is clicked. This is useful for providing example code that is "
"easy for the user to try out by just clicking the link.<br>"
"The optional parameters must be strings, and each is a \"see also\" "
"reference that will appear below the main help text. Any that begin "
"with \"help:\" are cross-references to other help() calls, e.g. "
"\"help:Lob\", which will make a link to the help(Lob) content. Any that "
"do not begin with \"help:\" become links that search for the string in "
"the online Lurch Doxygen documentation at "
"<a href='http://lurch.sourceforge.net/doxygen/html/'>"
"http://lurch.sourceforge.net/doxygen/html/</a>.<br>"
"<a href='execute var f = function () { return 1; };\\n"
"setHelp( f, "Example text.<br><a href='execute "
"2+2'>Try this example</a>",\\n"
" "help:Lob", "foo" );\\n"
"help( f )'>Click here to see an example in the console.</a><br>"
"See also <a href='execute help(copyHelp)'>copyHelp</a>." );
engine.globalObject().setProperty( "setHelp", f );
f = engine.newFunction( scriptCopyHelp );
f.setProperty( "helpHTML",
"Use <tt>copyHelp(x,y)</tt> to copy the help HTML about object x to "
"object y as well. This is usually only useful when x and y are two "
"different names for the same thing." );
engine.globalObject().setProperty( "copyHelp", f );
f = engine.newFunction( scriptMakeLurchURN );
f.setProperty( "helpHTML",
"A function for forming Lurch URNs from a title-author-language-version "
"quadruple. See <a href='DOX:class_lob.html'>the Lob class</a> documentation "
"online for information about Lurch URNs. See also "
"<a href='execute help(splitLurchURN)'>splitLurchURN</a>.");
engine.globalObject().setProperty( "makeLurchURN", f );
f = engine.newFunction( scriptSplitLurchURN );
f.setProperty( "helpHTML",
"A function for splitting Lurch URNs into title-author-language-version "
"quadruples. See <a href='DOX:class_lob.html'>the Lob class</a> documentation "
"online for information about Lurch URNs. See also "
"<a href='execute help(makeLurchURN)'>makeLurchURN</a>.");
engine.globalObject().setProperty( "splitLurchURN", f );
f = engine.newFunction( scriptHelpHistory );
f.setProperty( "helpHTML",
"A function for internal use by the Lurch Developer help system." );
engine.globalObject().setProperty( "helpHistory", f );
engine.globalObject().setProperty( "globalNamespace", engine.globalObject() );
}
QStringList globalFixtures ()
{
QScriptEngine e;
QStringList result;
QScriptValueIterator it( e.globalObject() );
while ( it.hasNext() ) {
it.next();
result << it.name();
}
return result;
}
void copyProperties ( QScriptValue from, QScriptValue to )
{
static QStringList permanents = globalFixtures();
QScriptValueIterator it( from );
while ( it.hasNext() ) {
it.next();
if ( !permanents.contains( it.name() ) )
to.setProperty( it.name(), it.value() );
}
}
bool member ( QScriptValue element, QList<QScriptValue> list )
{
foreach ( const QScriptValue& listelement, list )
if ( element.equals( listelement ) )
return true;
return false;
}
void debugDeepProperties_recur ( QScriptValue v, QList<QScriptValue> past, int indent )
{
QScriptValueIterator it( v );
while ( it.hasNext() ) {
it.next();
if ( v.propertyFlags( it.name() ) & QScriptValue::SkipInEnumeration )
continue;
bool done = member( it.value(), past );
past << v;
qDebug() << QString( "%1%2%3" ).arg( QString( 4*indent, ' ' ) )
.arg( it.name() ).arg( done ? "*" : "" );
/*
qDebug() << QString( "%1%2%3 (flags==%4)" ).arg( QString( 4*indent, ' ' ) )
.arg( it.name() ).arg( done ? "*" : "" )
.arg( v.propertyFlags( it.name() ) );
*/
if ( !done )
debugDeepProperties_recur( it.value(), past, indent + 1 );
}
}
void debugDeepProperties ( QScriptValue v )
{
QList<QScriptValue> tmp;
debugDeepProperties_recur( v, tmp, 0 );
}