Last active
May 18, 2018 03:00
-
-
Save mossheim/8dec64df924224913b4f60dbc5c3641d to your computer and use it in GitHub Desktop.
SuperCollider Annotations example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Annotations concept sketch | |
// Brian Heim, May 2018 | |
// Annotations are source-level metadata about a function or class. | |
// The Annotations class provides a hacky way of getting runtime reflection on annotations | |
// by parsing source code directly. Also provided are convenience functions on Method and Class. | |
Annotations { | |
// Get the annotations for a given Method or Class object, by parsing the relevant directly. | |
// Conveniently, Method and Class have the same interface here, so we don't need separate methods. | |
// Returns a Set of annotations. | |
*of { |obj| | |
var fileText, lines, index, annotations; | |
try { | |
fileText = File.readAllString(obj.filenameSymbol.asString); | |
} { |e| | |
Error("Could not read source file for '%': %".format(obj, e.what)).throw | |
}; | |
^this.prGetAnnotations(fileText, obj.charPos).as(Set); | |
} | |
// Get the annotations from source text. | |
// This method begins searching backwards from the line before `pos` until it reaches a line that is | |
// not an annotation. | |
*prGetAnnotations { |text, pos| | |
var index, lines; | |
var annotations = [], note; | |
lines = text.split($\n); | |
index = this.prLineBefore(lines, pos); | |
while { index >= 0 } { | |
note = this.prGetAnnotation(lines[index]); | |
if (note.notNil) { | |
annotations = annotations.add(note) | |
} { | |
^annotations | |
}; | |
index = index - 1 | |
}; | |
^annotations | |
} | |
// Index of line before the one containing pos. | |
// Lines is an array of lines of text, pos is an integer index into the entire text. | |
*prLineBefore { |lines, pos| | |
var lineIndices = (lines.collect(_.size) + 1).integrate; | |
^lineIndices.indexInBetween(pos).floor.asInteger; | |
} | |
// Returns an annotation as a symbol if str has an annotation; otherwise, returns nil | |
// An annotated line begins with optional leading whitespace and a comment. The first | |
// non-space character in the comment must be @. The remainder of the line is the annotation. | |
*prGetAnnotation { |str| | |
var match; | |
match = str.findRegexp("^\\s*//\\s*@"); | |
if (match.isEmpty) { ^nil } { ^str[match[0][1].size..].asSymbol }; | |
} | |
} | |
// Use to test Annotations concept: | |
// AnnotationExample.methods.collect(_.annotations) | |
// AnnotationExample.annotations | |
// | |
// @AnnotatedClass | |
AnnotationExample { | |
// @Melodrama | |
// @JunkyNonsense | |
// @ExampleMethod | |
isAnnotated { ^thisMethod.annotations } | |
// @ExpectedFailure | |
worldPeace { ^Error("...").throw } | |
unannotated { ^thisMethod.annotations.postln } | |
} | |
TestAnnotations : UnitTest { | |
test_class { | |
this.assertEquals(AnnotationExample.annotations, Set['AnnotatedClass']) | |
} | |
test_method_oneAnnotation { | |
this.assertEquals(AnnotationExample.findMethod('worldPeace').annotations, Set['ExpectedFailure']) | |
} | |
test_method_multiAnnotation { | |
var expected = Set['Melodrama', 'JunkyNonsense', 'ExampleMethod']; | |
this.assertEquals(AnnotationExample.findMethod('isAnnotated').annotations, expected) | |
} | |
test_method_unannotated { | |
this.assertEquals(AnnotationExample.findMethod('unannotated').annotations, Set[]) | |
} | |
} | |
+ Method { | |
// List of annotations for this method | |
annotations { | |
^Annotations.of(this) | |
} | |
// Whether this method has the given annotation | |
hasAnnotation { |annotation| | |
^this.annotations.includes(annotation); | |
} | |
} | |
+ Class { | |
// List of annotations for this method | |
annotations { | |
^Annotations.of(this) | |
} | |
// Whether this class has the given annotation | |
hasAnnotation { |annotation| | |
^this.annotations.includes(annotation); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment