diff --git a/lib/rouge/demos/supercollider b/lib/rouge/demos/supercollider new file mode 100644 index 0000000000..23a8e4b70b --- /dev/null +++ b/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; diff --git a/lib/rouge/guessers/disambiguation.rb b/lib/rouge/guessers/disambiguation.rb index 0bf14bb234..4767291a3f 100644 --- a/lib/rouge/guessers/disambiguation.rb +++ b/lib/rouge/guessers/disambiguation.rb @@ -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 diff --git a/lib/rouge/lexers/supercollider.rb b/lib/rouge/lexers/supercollider.rb new file mode 100644 index 0000000000..9a13610c4c --- /dev/null +++ b/lib/rouge/lexers/supercollider.rb @@ -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 + diff --git a/spec/lexers/python_spec.rb b/spec/lexers/python_spec.rb index 9dd0b65361..de0139754d 100644 --- a/spec/lexers/python_spec.rb +++ b/spec/lexers/python_spec.rb @@ -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 diff --git a/spec/lexers/supercollider_spec.rb b/spec/lexers/supercollider_spec.rb new file mode 100644 index 0000000000..a1bd9dc5aa --- /dev/null +++ b/spec/lexers/supercollider_spec.rb @@ -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 diff --git a/spec/visual/samples/supercollider b/spec/visual/samples/supercollider new file mode 100644 index 0000000000..fd3c29beab --- /dev/null +++ b/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; +// */