Strange `preProcessor` quirks

Would be very grateful for help:

// Works, but for some reason re-eval the 1st line toggles it on / off:
thisProcess.interpreter.preProcessor = { |c| c.replace("foo", "bar") };
"foobar".postln;

// Reset
thisProcess.interpreter.preProcessor = nil;

// Does not replace on the 1st eval, consecutive evals alternate:
(
thisProcess.interpreter.preProcessor = { |c| c.replace("foo", "bar") };
"foobar".postln;
)

Is it possible to activate it with the code to process in one go?
I’ve tried separate functions, forks, and swearing so far.
Also, I’m afraid to ask why it toggles, even @ 1st example.

Evaluate that first line twice and then run

thisProcess.interpreter.preProcessor.postcs

You will see

{ |c| c.replace("bar", "bar") }

Why did this happen? Well, the old preProcessor replaced “foo” with “bar” as you asked it to…

1 Like

The reason 2nd example doesn’t change “foo” on first evaluation is that the whole thing has already passed through the (nil) preProcessor by the time it gets interpreted. If you really need to set the preProcessor and then immediately have it process something else in the same block, here’s one way to hack it. But I really don’t recommend getting stuck in this, as you’ve already seen it’s a good recipe for puzzlement.

(
thisProcess.interpreter.preProcessor = { |c| c.replace("foo", "bar") };
thisProcess.interpreter.cmdLine = "\"foo\".postln";
thisProcess.interpreter.interpretPrintCmdLine;
)
1 Like

Eric, your first example blew my mind — the fact that it replaces it in itself is quite logical when you think about it, but not the first time you see it :slight_smile: preProcessorCeption…

I’m new too SC, just the first day, forgetting .ar after oscillators every other time ^^. You seem rather knowledgeable, maybe you can hint me in the right direction — I simply want a class that preprocesses custom tags and evals the arg function on a single call. If I just put both the preprocessor and .() the function — it works only on the 2nd call, like my () example above.

Should I use your idea with cmdLines, or maybe there is already some kind of established approach for that?

I.e. MyClass({ customTag }) → replace the tag and .() the whole thing.

Also, I get the sense that thisProcess.interpreter.preProcessor has a wider scope than this.preProcessor, but not sure, and the docs don’t help. Would be very grateful for a hint.

The general process is, essentially, myPreproc.value(codeString).interpret where codeString is " ... your code ...".

The catch is that writing code into a string literal needs escape characters – "c.replace(\"foo\", \"bar\")" – which is inconvenient. But if you can extract text from the code document by other means (e.g. cmdLine) then you can do what you like with it.

The problem with your original approach is that the preProcessor update statement and the code block that it’s supposed to apply to are compiled into one function. The behavior you were hoping for was to execute that function partially – only doing the preprocessor bit – then drop out of that and recompile the rest of it.

One idea might be to define a syntax to wrap the your eventual preprocessor, and handle this in the main preprocessor:

###
... implicitly has a "code" argument...
... do stuff to "code"...
###

... and now the target code...

The “real” preprocessor would find the ### … ### bit and wrap it like "{ |code| " ++ theString ++ " }", .interpret this, and stash it in another location (as a secondary preprocessor). Then the real preprocessor would grab the rest of the code string, run the secondary preprocessor on it, and return this back to be executed.

It’s convoluted but I think there is no way to do this without splitting the cmdLine text into two sections, and handling them differently.

hjh

2 Likes

Maybe something like this?

(
{
  var fooString = "foo";
  (fooString ++ " is the contents of fooString").postln;
}
.asCompileString.replace("foo", "bar").interpret.value;
)

the result of that .interpret will be the equivalent function but with all instances of foo (including the variable name itself) replaced – so make sure you don’t also have a barString variable declared.

this == thisProcess.interpreter // -> true

wow you’ve really gone in deep :slight_smile:

Note that the fooString approach could be seen as a workaround for not splitting the code into two parts in the first place. All single-block approaches to this problem will suffer the same weaknesses.

hjh

Here’s how I would do it:

(
this.preProcessor = { |code|
	// 1. find a new preprocessor def
	var i = code.find("###");
	var j;
	if(i.notNil) {
		j = code.find("###", offset: i+1);
		if(j.notNil) {
			Library.put(\preproc,
				(
					"{ |code| "
					++ code[i+4 .. j-1]
					++ " }"
				).interpret
			);
			code = code[.. i-1] ++ code[j+4 ..];
		}
	};
	// 2. apply the preprocessor to the rest of the code block
	Library.at(\preproc).value(code) ?? { code };
};
)

// run this as one line
### code.replace("1", "2") ### 1 + 1
-> 4

### code.replace("1", "3.75") ### 1 + 1
-> 7.5

The ### delimited preprocessor could span multiple lines too.

(But it still has the problem that setting the preprocessor interferes with future attemps to set the preprocessor. Simple string-replacement is likely to have this problem. Syntax-aware scanning would fix it but that’s quite a lot harder, maybe not worth it at this point.)

hjh

1 Like

You guys are most kind and smart — big hearts, big brains — nice meeting you. Been seeing the “hjh” signature on quite a lot of helpful content, dating back to mailing lists. I’ll go thorough your helpful suggestions, and keep an eye on this for the most elegant solution.

I’m not really going deep per se, just want to simplify typing:

1 kick          // → s.bind(Synth(\kick, [dur: 1]));
1 kick!         // → s.bind(Synth(\kick, [dur: 1])); 1.wait;
2 bass @ hz: 55 // → s.bind(Synth(\bass, [dur: 2, hz: 55])); 

Basically a regex macro to save on typing. I am a fan of Nathan Ho’s approach of not using proxies & events (after I almost lost my marbles not being able to stop long events in Pbind without using the group stop hack), but don’t like to type playParallel etc like he does.

So far I’m mostly making Euclidean fart noises. If I ever meet Euclid or Godfried Toussaint in aftrerlife, they’d ghost me.

But SuperCollider is most super, in both sounds and users <3