Skip to content

Commit

Permalink
Add SuperCollider lexer (#749)
Browse files Browse the repository at this point in the history
This adds support for the SuperCollider language. As the standard file  
extension for SuperCollider files (`.sc`) was already registered to the 
Python lexer, this commit also adds a disambiguation.
  • Loading branch information
mossheim authored and pyrmont committed Jun 6, 2019
1 parent 7bd180f commit f494b53
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 0 deletions.
11 changes: 11 additions & 0 deletions lib/rouge/demos/supercollider
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// modulate a sine frequency and a noise amplitude with another sine
// whose frequency depends on the horizontal mouse pointer position
~myFunction = {
var x = SinOsc.ar(MouseX.kr(1, 100));
SinOsc.ar(300 * x + 800, 0, 0.1)
+
PinkNoise.ar(0.1 * x + 0.1)
};

~myFunction.play;
"that's all, folks!".postln;
7 changes: 7 additions & 0 deletions lib/rouge/guessers/disambiguation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ def match?(filename)

Plist
end

disambiguate '*.sc' do
next Python if matches?(/^#/)
next SuperCollider if matches?(/(?:^~|;$)/)

next Python
end
end
end
end
116 changes: 116 additions & 0 deletions lib/rouge/lexers/supercollider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
class SuperCollider < RegexLexer
tag 'supercollider'
filenames '*.sc', '*.scd'

title "SuperCollider"
desc 'A cross-platform interpreted programming language for sound synthesis, algorithmic composition, and realtime performance'

def self.keywords
@keywords ||= Set.new %w(
var arg classvar const super this
)
end

# these aren't technically keywords, but we treat
# them as such because it makes things clearer 99%
# of the time
def self.reserved
@reserved ||= Set.new %w(
case do for forBy loop if while new newCopyArgs
)
end

def self.constants
@constants ||= Set.new %w(
true false nil inf thisThread
thisMethod thisFunction thisProcess
thisFunctionDef currentEnvironment
topEnvironment
)
end

state :whitespace do
rule /\s+/m, Text
end

state :comments do
rule %r(//.*?$), Comment::Single
rule %r(/[*]) do
token Comment::Multiline
push :nested_comment
end
end

state :nested_comment do
rule %r(/[*]), Comment::Multiline, :nested_comment
rule %r([*]/), Comment::Multiline, :pop!
rule %r([^*/]+)m, Comment::Multiline
rule /./, Comment::Multiline
end

state :root do
mixin :whitespace
mixin :comments

rule /[\-+]?0[xX]\h+/, Num::Hex

# radix float
rule /[\-+]?\d+r[0-9a-zA-Z]*(\.[0-9A-Z]*)?/, Num::Float

# normal float
rule /[\-+]?((\d+(\.\d+)?([eE][\-+]?\d+)?(pi)?)|pi)/, Num::Float

rule /[\-+]?\d+/, Num::Integer

rule /\$(\\.|.)/, Str::Char

rule /"([^\\"]|\\.)*"/, Str

# symbols (single-quote notation)
rule /'([^\\']|\\.)*'/, Str::Other

# symbols (backslash notation)
rule /\\\w+/, Str::Other

# symbol arg
rule /[A-Za-z_]\w*:/, Name::Label

rule /[A-Z]\w*/, Name::Class

# primitive
rule /_\w+/, Name::Function

# main identifiers section
rule /[a-z]\w*/ do |m|
if self.class.keywords.include? m[0]
token Keyword
elsif self.class.constants.include? m[0]
token Keyword::Constant
elsif self.class.reserved.include? m[0]
token Keyword::Reserved
else
token Name
end
end

# environment variables
rule /~\w+/, Name::Variable::Global

rule /[\{\}()\[\];,\.]/, Punctuation

# operators. treat # (array unpack) as an operator
rule /[\+\-\*\/&\|%<>=]+/, Operator
rule /[\^:#]/, Operator

# treat curry argument as a special operator
rule /\b_\b/, Name::Builtin
end
end
end
end

4 changes: 4 additions & 0 deletions spec/lexers/python_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
it 'guesses by filename' do
assert_guess :filename => 'foo.py'
assert_guess :filename => 'foo.pyw'
assert_guess :filename => '*.sc', :source => '# A comment'
assert_guess :filename => 'SConstruct'
assert_guess :filename => 'SConscript'
assert_guess :filename => 'foo.tac'
end

it 'guesses by mimetype' do
Expand Down
16 changes: 16 additions & 0 deletions spec/lexers/supercollider_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

describe Rouge::Lexers::SuperCollider do
let(:subject) { Rouge::Lexers::SuperCollider.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.scd'
assert_guess :filename => 'foo.sc', :source => '~x = 3'
assert_guess :filename => 'foo.sc', :source => '0;'
end
end
end
166 changes: 166 additions & 0 deletions spec/visual/samples/supercollider
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// modulate a sine frequency and a noise amplitude with another sine
// whose frequency depends on the horizontal mouse pointer position
~myFunction = {
var x = SinOsc.ar(MouseX.kr(1, 100));
SinOsc.ar(300 * x + 800, 0, 0.1)
+
PinkNoise.ar(0.1 * x + 0.1)
};

~myFunction.play;
"that's all, folks!".postln;

/* Test Class */
Collection {
*newFrom { | aCollection |
var newCollection = this.new(aCollection.size);
aCollection.do {| item | newCollection.add(item) };
^newCollection
}
*with { | ... args |
var newColl;
// answer a collection of my class of the given arguments
// the class Array has a simpler implementation
newColl = this.new(args.size);
newColl.addAll(args);
^newColl
}
*fill { | size, function |
var obj;
if(size.isSequenceableCollection) { ^this.fillND(size, function) };
obj = this.new(size);
size.do { | i |
obj.add(function.value(i));
};
^obj
}
*fill2D { | rows, cols, function |
var obj = this.new(rows);
rows.do { |row|
var obj2 = this.new(cols);
cols.do { |col|
obj2 = obj2.add(function.value(row, col))
};
obj = obj.add(obj2);
};
^obj
}
*fill3D { | planes, rows, cols, function |
var obj = this.new(planes);
planes.do { |plane|
var obj2 = this.new(rows);
rows.do { |row|
var obj3 = this.new(cols);
cols.do { |col|
obj3 = obj3.add(function.value(plane, row, col))
};
obj2 = obj2.add(obj3);
};
obj = obj.add(obj2);
};
^obj
}

// from Array
mirror2 {
_ArrayMirror2
^this.primitiveFailed
}
}

// class inheritance
MyClass : Object {
classvar <>decorations;
var <>igloos;
const magicNumber = 3;
}

/* This is a multiline comment
/* With nesting! */
End of multiline comment */

~environmentVariable = { 3.do(_.postln) };

~environmentVariable.value();

~myFunction = { arg size, offset, freq;
postln("Bye!");
};

// function calls with symbol arguments:
s.boot(startAliveThread: false, recover: true);
Array.geom(size: 5, start: 1, grow: 8);
[ 1, 8, 64, 512, 4096 ].collect { arg n;
n.squared
};

// array unpacking
#a, b, c = [1, 4, 9];

// some numbers
// integers
0;
10;
33;
-235;
0x15;
0x159abcdef;
0x159ABCDEF;
2r01;
8r711;
36rabczyblkgh;
36rAZ19GH;

// floats
0.0;
0.0e5;
1e5;
1e-5;
0.5e-5;
10r2034.5;
36r2358.ABCDZ;
-36r2358.ABCDZ;
15pi;
15 pi;
-10 pi;
-10pi;
-1.5e7pi;

// strings
"abcdefg hijklmnop";
"\"quoted\"";
"\ttabbed\t";
"\\\ttabbed with backslashes\t\\";

// symbols
\abc;
\a1;
\a_symbol;
'a symbol';
'a cl3v3r\'_symb0l"';

// characters
$a;
$ ;
$b;
$0;
$$;
$\t;
$\0;
$\&;
$\\;

// curry argument vs underscore in name
[1, 2, 3].do(_.postln);
[1, 2, 3].collect(_ + _);
variable_name.function_name(Class_Name);

// comments
/* abc */ notComment;
/* /* */ comment */ notComment;
/* /* * */ comment */ notComment;
/* /*/ comment */ comment */ notComment;
/* /**/ comment */ notComment;
// /*
notComment;
// */

0 comments on commit f494b53

Please sign in to comment.