Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SuperCollider language support #749

Merged
merged 20 commits into from Jun 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/rouge/demos/supercollider
@@ -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
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
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

pyrmont marked this conversation as resolved.
Show resolved Hide resolved
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
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
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

pyrmont marked this conversation as resolved.
Show resolved Hide resolved
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
@@ -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;
// */