Chris Stern's BBEdit Python language module.
diff --git a/Mac/Contrib/BBPy.lm/BBPy.c b/Mac/Contrib/BBPy.lm/BBPy.c
new file mode 100644
index 0000000..85f0dd2
--- /dev/null
+++ b/Mac/Contrib/BBPy.lm/BBPy.c
@@ -0,0 +1,456 @@
+#include <AEDataModel.h>
+
+#define DEBUG 0
+
+#define kComponentSignatureString "BBPy.LM"
+#include <Debugging.h>
+
+
+#include <BBLMInterface.h>
+#include <BBXTInterface.h>
+//#include <BBLMTextIterator.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <Sound.h>
+
+#if DEBUG
+void debugf_(const char* func,const char* fileName,long line, const char*fmt,...)
+{
+ va_list arg;
+ char msg[256];
+ va_start(arg, fmt);
+ vsnprintf(msg,256 ,fmt, arg);
+ DebugAssert(COMPONENT_SIGNATURE, DEBUG_NO_OPTIONS, kComponentSignatureString ": " , msg, nil, fileName, line, 0 );
+
+ //debug_string(msg);
+}
+#define debugf(FMT,...) debugf_( __FUNCTION__,__FILE__, __LINE__,FMT,__VA_ARGS__);
+#else
+#define debugf(FMT,...)
+#endif
+
+typedef const char *Str;
+
+
+enum{
+ kPyBBLMStringSubst = kBBLMFirstUserRunKind
+};
+
+#define iswordchar(x) (isalnum(x)||x=='_')
+
+
+struct runloc{
+ bool past_gap;
+ long pos;
+ long last_start;
+ unsigned char*p;
+};
+
+char start(struct runloc& r,BBLMParamBlock &pb)
+{
+ r.past_gap = false;
+ r.last_start = pb.fCalcRunParams.fStartOffset;
+ r.pos = pb.fCalcRunParams.fStartOffset;
+ r.p = ((unsigned char*)pb.fText) + pb.fCalcRunParams.fStartOffset;
+ // Adjust for the gap if we¹re not already past it.
+ if ((!r.past_gap) && (r.pos >= pb.fTextGapLocation)){
+ r.p += pb.fTextGapLength;
+ r.past_gap = true;
+ }
+ return *r.p;
+
+}
+
+char nextchar(struct runloc&r,BBLMParamBlock &pb)
+{
+ if ( r.pos< pb.fTextLength){
+ ++r.pos;
+ ++r.p;
+ if ((!r.past_gap) && (r.pos >= pb.fTextGapLocation)){
+ r.p += pb.fTextGapLength;
+ r.past_gap = true;
+ }
+ return *r.p;
+ }
+ else{
+ return 0;
+ }
+}
+
+bool addRun(BBLMRunCode kind, int start,int len , const BBLMCallbackBlock& bblm_callbacks)
+{
+ if (len > 0){ // Tie off the code run we were in, unless the length is zero.
+ debugf("Run %d %d:%d", kind, start, start+len-1 );
+ return bblmAddRun( &bblm_callbacks, 'Pyth',
+ kind, start, len, false);
+
+ }
+ else{
+ return true;
+ }
+}
+
+bool addRunBefore (BBLMRunCode kind,struct runloc& r, const BBLMCallbackBlock& bblm_callbacks)
+{
+ bool more_runs = addRun(kind, r.last_start, r.pos - r.last_start, bblm_callbacks);
+ r.last_start = r.pos;
+ return more_runs;
+}
+
+bool addRunTo (BBLMRunCode kind, struct runloc& r, const BBLMCallbackBlock& bblm_callbacks)
+{
+ bool more_runs = addRun(kind, r.last_start, r.pos - r.last_start+1, bblm_callbacks);
+ r.last_start = r.pos+1;
+ return more_runs;
+}
+
+
+bool colorstr( char delim,
+ BBLMParamBlock &pb,
+ struct runloc &r,
+ const BBLMCallbackBlock &bblm_callbacks)
+{
+ bool tripple = false , pers = false, lookup = false, more_runs = true;
+ char c = nextchar(r,pb);
+
+ if (c == delim){
+ c = nextchar(r,pb);
+ if (c == delim){
+ tripple = true;
+ c = nextchar(r,pb);
+ }
+ else{
+ //double
+ return addRunBefore(kBBLMRunIsSingleString,r,bblm_callbacks);
+ }
+ }
+ while (c && more_runs){
+ if (pers ){
+ if (isalpha(c)){
+ more_runs = addRunTo(kPyBBLMStringSubst,r,bblm_callbacks);
+ }
+ else if (c == '('){
+ lookup = true;
+ }
+ }
+ pers = false;
+ if (c == delim){
+ if (tripple){
+ if ((c = nextchar(r,pb))== delim && (c = nextchar(r,pb)) == delim){
+ break; // end of tripple-quote.
+ }
+ }
+ else{
+ break; // end of single-quote.
+ }
+
+ }
+ else if (c== '\\'){
+ nextchar(r,pb);
+ }
+ else if (c=='\r'||c=='\n'){
+ if (!tripple){
+ break;
+ }
+ }
+ else if (c=='%'){
+ more_runs = addRunBefore(kBBLMRunIsSingleString,r,bblm_callbacks);
+ pers = true;
+ }
+ else if (c==')' && lookup){
+ more_runs = addRunTo(kPyBBLMStringSubst,r,bblm_callbacks);
+ lookup = false;
+ }
+ c = nextchar(r,pb);
+ }
+ return more_runs && addRunTo(lookup?kPyBBLMStringSubst:kBBLMRunIsSingleString,r,bblm_callbacks);
+}
+
+bool colorcomment(BBLMParamBlock &pb,
+ struct runloc &r,
+ const BBLMCallbackBlock &bblm_callbacks)
+{
+ while (char c = nextchar(r,pb)){
+ if (c=='\r'|| c=='\n'){
+ break;
+ }
+ }
+ return addRunTo(kBBLMRunIsLineComment,r,bblm_callbacks);
+}
+
+void CalculateRuns(BBLMParamBlock &pb,
+ const BBLMCallbackBlock &bblm_callbacks)
+
+{
+ const struct rundesc *state = NULL;
+ bool more_runs=true;
+
+ struct runloc r;
+
+ char c = start(r,pb);
+
+ while (c && more_runs){
+ loop:
+ // Process a char
+ if (state==NULL){
+ //If we're in the basic 'code' state, check for each interesting char (rundelims[i].start).
+ switch (c){
+ case '\'':
+ case '"':
+ more_runs = addRunBefore(kBBLMRunIsCode,r,bblm_callbacks);
+ if (more_runs){
+ more_runs = colorstr(c,pb,r,bblm_callbacks);
+ }
+ break;
+ case '#' :
+ more_runs = addRunBefore(kBBLMRunIsCode,r,bblm_callbacks);
+ if (more_runs){
+ more_runs = colorcomment(pb,r,bblm_callbacks);
+ }
+ break;
+ default:
+ break;
+ }
+
+ }
+ c = nextchar(r,pb);
+ }
+ if (more_runs){
+ addRunBefore(kBBLMRunIsCode,r,bblm_callbacks);
+ }
+
+
+}
+static void AdjustRange(BBLMParamBlock ¶ms,
+ const BBLMCallbackBlock &callbacks)
+{
+ DescType language;
+ BBLMRunCode kind;
+ SInt32 charPos;
+ SInt32 length;
+ UInt32 index = params.fAdjustRangeParams.fStartIndex;
+
+ while( index > 0 &&
+ bblmGetRun(&callbacks, index, language, kind, charPos, length) &&
+ (kind==kPyBBLMStringSubst||kind==kBBLMRunIsSingleString)){
+ index--;
+ };
+ params.fAdjustRangeParams.fStartIndex = index;
+}
+
+
+// The next couple funcs process the text of a file assumming it's in 1 piece in memory,
+// so they may not be called from CalculateRuns.
+
+bool matchword(BBLMParamBlock &pb, const char *pat ,unsigned long *pos)
+{
+ const char *asciText = (const char *) (pb.fTextIsUnicode?NULL:pb.fText);
+
+ int i;
+ for (i=0; pat[i]; i++){
+ if (*pos+i>=pb.fTextLength){
+ return false;
+ }
+ if (asciText[*pos+i] != pat[i]){
+ return false;
+ }
+ }
+ if ((*pos+i<pb.fTextLength)&&iswordchar(asciText[*pos+i])){
+ return false;
+ }
+ *pos+=i;
+ return true;
+}
+
+int matchindent(BBLMParamBlock &pb, UInt32 *pos)
+{
+ const char *asciText = (const char *) (pb.fTextIsUnicode?NULL:pb.fText);
+ int indent=0;
+
+ while(*pos<pb.fTextLength){
+ switch (/*(char)(pb.fTextIsUnicode?uniText[pos]:*/asciText[*pos]/*)*/){
+ case ' ':
+ ++*pos;
+ indent++;
+ break;
+ case '\t':
+ ++*pos;
+ indent+=8;
+ break;
+ case '#':
+ return -1;
+ break;
+ default:
+ return indent;
+ break;
+ }
+ }
+}
+
+
+void eat_line(BBLMParamBlock &pb, unsigned long* pos)
+{
+ const char *asciText = (const char *) (pb.fTextIsUnicode?NULL:pb.fText);
+ while (asciText[*pos]!='\r' && asciText[*pos]!='\n' && *pos<pb.fTextLength) {++*pos;}
+ while ((asciText[*pos]=='\r' || asciText[*pos]=='\n') && *pos<pb.fTextLength) {++*pos;}
+
+}
+
+void addItem(BBLMParamBlock &pb, UInt32 pos, int nest, BBLMFunctionKinds kind,
+ const BBLMCallbackBlock *bblm_callbacks)
+{
+ UInt32 funcstartpos = pos;
+ UInt32 funcnamelen=0;
+ UInt32 offset=0;
+ const char *asciText = (const char *) pb.fText;
+ UInt32 index;
+ OSErr err;
+
+ while (isspace(asciText[pos]) && pos<pb.fTextLength) {++pos;}
+ UInt32 fnamestart = pos;
+ while ((isalnum(asciText[pos])||asciText[pos]=='_') && pos<pb.fTextLength) {pos++; funcnamelen++;}
+
+ err = bblmAddTokenToBuffer( bblm_callbacks,
+ pb.fFcnParams.fTokenBuffer,
+ (void*)&asciText[fnamestart],
+ funcnamelen,
+ pb.fTextIsUnicode,
+ &offset);
+ BBLMProcInfo procInfo;
+ procInfo.fFunctionStart = fnamestart; // char offset in file of first character of function
+ procInfo.fFunctionEnd = pos; // char offset of last character of function
+
+ procInfo.fSelStart = fnamestart; // first character to select when choosing function
+ procInfo.fSelEnd = pos; // last character to select when choosing function
+
+ procInfo.fFirstChar = fnamestart; // first character to make visible when choosing function
+
+ procInfo.fKind = kind;
+
+ procInfo.fIndentLevel = nest; // indentation level of token
+ procInfo.fFlags = 0; // token flags (see BBLMFunctionFlags)
+ procInfo.fNameStart = offset; // char offset in token buffer of token name
+ procInfo.fNameLength = funcnamelen; // length of token name
+
+ err = bblmAddFunctionToList(bblm_callbacks,
+ pb.fFcnParams.fFcnList,
+ procInfo,
+ &index);
+}
+
+
+
+enum{
+ maxnest=5
+};
+
+void ScanForFunctions(BBLMParamBlock &pb,
+ const BBLMCallbackBlock &bblm_callbacks)
+{
+
+ const char *asciText = (const char *) (pb.fTextIsUnicode?NULL:pb.fText);
+ UniCharPtr uniText = (UniCharPtr) (pb.fTextIsUnicode?pb.fText:NULL);
+
+ int indents[maxnest]= {0};
+ int nest = 0;
+
+ UInt32 pos=0; // current character offset
+
+
+ while (pos<pb.fTextLength){
+
+ int indent = matchindent(pb, &pos);
+
+ if (indent >= 0){
+ for (int i=0; i <= nest; i++){
+ if (indent<=indents[i]){
+ nest = i;
+ indents[nest]=indent;
+ goto x;
+ }
+ }
+ indents[++nest]=indent;
+ x:
+
+ if (matchword(pb,"def",&pos)){
+ addItem( pb, pos, nest, kBBLMFunctionMark, &bblm_callbacks);
+ }
+ else if (matchword(pb, "class", &pos)){
+ addItem( pb, pos, nest, kBBLMTypedef, &bblm_callbacks);
+ }
+ }
+ eat_line(pb,&pos);
+ }
+
+}
+
+OSErr main( BBLMParamBlock ¶ms,
+ const BBLMCallbackBlock &bblm_callbacks,
+ const BBXTCallbackBlock &bbxt_callbacks)
+{
+ OSErr result;
+
+ if ((params.fSignature != kBBLMParamBlockSignature) ||
+ (params.fLength < sizeof(BBLMParamBlock)))
+ {
+ return paramErr;
+ }
+
+ switch (params.fMessage)
+ {
+ case kBBLMInitMessage:
+ case kBBLMDisposeMessage:
+ {
+ result = noErr; // nothing to do
+ break;
+ }
+
+ case kBBLMCalculateRunsMessage:
+ CalculateRuns(params, bblm_callbacks);
+ result = noErr;
+ break;
+
+ case kBBLMScanForFunctionsMessage:
+ ScanForFunctions(params, bblm_callbacks);
+ result = noErr;
+ break;
+
+ case kBBLMAdjustRangeMessage:
+ AdjustRange(params, bblm_callbacks);
+ result = noErr;
+ break;
+
+ case kBBLMMapRunKindToColorCodeMessage:
+ switch (params.fMapRunParams.fRunKind){
+ case kPyBBLMStringSubst:
+ params.fMapRunParams.fColorCode = kBBLMSGMLAttributeNameColor;
+ params.fMapRunParams.fMapped = true;
+ break;
+ default:
+ params.fMapRunParams.fMapped = false;
+ }
+ result = noErr;
+ break;
+
+ case kBBLMEscapeStringMessage:
+ case kBBLMAdjustEndMessage:
+ case kBBLMMapColorCodeToColorMessage:
+ case kBBLMSetCategoriesMessage:
+ case kBBLMMatchKeywordMessage:
+ {
+ result = userCanceledErr;
+ break;
+ }
+
+ default:
+ {
+ result = paramErr;
+ break;
+ }
+ }
+ return result;
+}
\ No newline at end of file