Re: circumflex/caret --> scoped crm114 script variables

Ger Hobbelt <[email protected]>
Newsgroups gmane.mail.spam.crm114
Message-ID <[email protected]>
On Wed, Feb 25, 2009 at 3:54 PM, Bill Yerazunis <[email protected]> wrote:
>   WARNING: the latest crm114-GerH supports scoped script variables, so a
>   particular (ugly) variable instantiation use in scripts won't work any
>   longer: see
>
>   http://hebbut.net/Public.Offerings/crm114/crm114-BlameBarack-Branch-preliminary.html#scoped_variables
>
>
> Whoa!!!!  Interesting!
>
> I tried something like that but got tied up in knots.  For example:
>
>  isolate (:a:) /Foo!/        # a is of global scope
>  call /:capture_func:/
>  output /:a: is :*:a:\n/
>
>  :capture_func:
>  isolate (:z:) /abcdefghijklmnop/           # make :z: be local
>  match [:z:] /...(...).../  ( :: :a: )      # capture :a: into :z:
>  return
>
> Now, :a: is attached to the 4th through 6th characters of :z: - but
> :z: is now _going away_.  So, what does :a: have?

What you'd expect: 'def'.
Hm, nice extra test case, BTW.


> Second question: are these scoped variables "dynamic" or "static" -
> that is, if I call a function with a locally scoped variable a second
> time, what is the value of a locally scoped variable?  Is it retained
> between calls (i.e. "static", as in C), or is the locally scoped
> variable reinitialized from scratch (so DEFAULT values values will
> trigger in!)

See that URL up there ;-)

The current release has them as 'static' but that is considered a bug,
NOT a feature.


Note that I come from the other direction at this: I got absolutely
fed up with having to keep track which bit of code 'isolate'd or
otherwise instanciated variables, which would then magically appear
everywhere. This was quite nauseating while I worked on
mailfilter/mailreaver/mailtrainer/maillib, as those have nontrivial
LOC counts, so the lack of _scope_ in this script language started to
seriously hurt.

When you've had a look at the latest few GerH releases, you'll find
that mail*.crm has undergone some changes, one part feature
unification, one part extra checking and Windows portability (path
conversion and such stuff) and one last part: kicking out the need for
syscall. It's got two drawbacks:
- it's slow (see the timing tests in GerH tests/ )
- it's very system dependent (there's no such thing as a bang line in
Windows and you can forget about running /usr/local/bin/crm114 as well
;-) )

And syscall is not needed after all: mailreaver can load maillib and
so can it load mailtrainer and call it's relevant code as a
subroutine....... if only we had scoped variables, for otherwise the
'magick' regarding sudden changes to existing variables becomes
unbearable.

>From a language design point of view, the scoping rules should come as
close to other, more well known languages, as possible.
So all this static, dynamic, etc. is all very nice, but let's be real:
serious software engineers know globals (and statics are just another
form of global) are Bad Form. The coding standards abound which forbid
this type of variable declaration entirely (and I'm not _that_ anal
(close, baby, _close_, though) and using reduced scope (local scope)
for your variables has been advocated worldwide for, what, 30? 40?
years?
Local scoped variables allow one to use Sound Coding Practices(tm) --
something crm114 can use in several areas ;-)

Anyway, the code tonight just passed several tests. Screw 'static'
scope, not gonna happen. Not on my watch, anyway.

So we have local scope (what you call 'dynamic'), the old fash 'global
scope' and a particular called 'outer scope'.

Let me explain: when variables are instantiated (by isolate or other
operations), by default, these variables are instantiated in 'local
scope'.
'Scope' is defined as the call depth, i.e. you enter a new (sub)scope
when you execute a 'call' operation and you 'pop' to the next 'outer
scope' when you execute a 'return' operation.

Notes here: 'trap' and the lot do NOT change scope. NOR do curly
braces (too bad for the C lovers; crm114 script isn't exactly 'C', so
I felt free to disregard that language for this.)


Then how do you access outer scope/global scope variables?
Where 'outer scope' is, for the reference point of the currently
active call depth (= scope) is just like 'global scope'.
Simple: when you execute commands which assume variables exist, such
variables are sought in local scope, and when not found there, the
outer scope levels are ascended until we either find the variable name
or run out of scope levels.
That means that your :a: up there is found, and it sits in the first
outer scope level, from the point of view of :capture_func:

Any edits to any variables are performed (and thus visible) at the
scope where they were instantiated. (So the edit made by the 'match'
to that ':a:' up there, will still be visible when :capture_func:
'return's.

Variable instantiation: by default 'isolate' instantiates a new
variable instance at local scope (if one doesn't exist already). Note
the 'by default' because there are a few exceptions to this rule:

isolate <default> (:var:) /.../

essentially is just another operation which *references* a variable --
due to the <default> -- , just like 'match', 'eval', etc.etc., so the
general rule applies there: the variable is looked up in local scope,
when not there, outer scopes are ascended until either found or we run
out of scopes.
When 'isolate <default> ...' then decides to instantiate the variable,
as it doesn't exist in any accessible scope, the 'instantiation' rule
applies: variables are instantiated BY DEFAULT in local scope.

So.... you'd love to instantiate a global variable, eh? Easy. Add
<keep> as attribute to the operation and you're switching to the
'augmented rule' for variable instantiation:
with <keep>, the variable is instantiated (WHEN it has to be
instantiated!) at GLOBAL scope (that's the top-most 'outer scope')

The following commands support <keep> from about a hour ago:

isolate
call   (the return variable!)
match  (the subexpr variables!)
union
intersect
eval   (the result var)
input

Note that eval, match, etc. often do not instantiate the variables
they fill with results, but they CAN, also in vanilla. Here, we just
add the possibility to have them instantiate the variable in 'topmost
outer scope' a.k.a. 'global scope'.

By sticking to either 'local scope' and 'outer scope' rules like that
the amount of detail you need to remember/learn when programming in
this language is kept minimal. And I'm a big fan when it comes to
smooching, especially when it comes to language design.

so

isolate <keep> (:a:)

will create a 'global scope' instance. Note the rules as described
above: IFF the variable is not already visible in the current scope
(by existing in the current=local scope or any of the visible outer
scopes.

This design allows to write structured code (which you should do). Use
'isolate' as the crm114 script equivalent of local scope variable
declarations as you know them from other languages. More precisely,
you should use this construct for that:

isolate (:var:) /.../

and don't 'forget' the /.../ -- like Bill did in several spots in his
scripts -- because in vanilla, GerH-old style and GerH-as-of-today all
treat
  isolate (:var:)
ever so slightly different from
  isolate (:var:) /.../
and
  isolate <default> (:var:) /.../



> Third question: if it's really scoped, then there should be a way
> to create that local variable as an overlay, so it _keeps_ it's
> value on return, like this
>
>   isolate (:a:) /foo/
>   call /:capture_func:
>   output /:*:a:/         # should be back to /foo/
>
>   :capture_func:
>   isolate (:a:) /bar/
>   output /:a:/
>   return

See above: <keep>'s your answer:

>   :capture_func:
>   isolate <keep> (:a:) /bar/
>   output /:a:/
>   return

>

> This drove me to consider that perhaps we needed four different waus
> for a variable to be created:
>
>   isolate   - global scope
>   dynamic   - local scope, state lost on RETURN
>   static    - local scope, retained state
>   match     - global by default, but will flow with dynamic or static.

Bill, please, I like complexity, but NOT in language design. Treat all
as equal: match, eval, insolate, anybody who (optionally) instantiates
variables does so at a given scope, which should be local scope as any
developer coming from other languages expects /that/; not some
fancy-oh-my-need-to-remember-this-exception-regarding-match-here. Yes,
that gave me hackles.


And yes, it breaks some crm114 scripts, but then my thoughts on those
are that they aren't exactly the pinnacle of coding excellence either
and shouldn't serve as examples how to write crm114 script software
for anyone.

Most notably, the load_cfg code in maillib croaks. A simple <keep> for
that isolate in there fixes things up nicely. Which is a hack.
The alternative is instantiating the variables up front in outer-most
scope, where they are expected to exist after all by the remainder of
the mail*.crm code. See the current GerH for that change. Though,
granted, plonking <keep> in there is less lines. So I'm thinking of
going with that for the next release, just to prove a point. Maybe.

Oh, and have a gander at the latest release anyway: mailfilter.cf also
supports [] next to // as value delimiters, of course. It's not just
isolate :-)






> In which case, doing a DYNAMIC or STATIC on a MATCHed variable
> does... what?  Do they create local overlays, in which case the old
> (MATCHed) value is restored on RETURN, or does it change the type of
> variable so they're erased (for dynamic) or hidden (for static) on
> return?

See above.


> And if it's a MATCH captured variable, are changes made while in the
> subroutine kept in the outer variable, or rolled back on RETURN?

Changes are kept... up to a point. Since you are suggesting something
here which simply is not a good coding practice anyway (but very nice
to have around for when we organize the first international OCCC
(Obfuscated CRM114 Coding Contest)) the details are a little hairy
there:

upon return, such variables point to data which is still valid -- in a
way. The fact is that the current implementation 'flags' variables
which go out of scope as 'out of scope'.
When you enter that scope level again (or a deeper scope depth level)
and attempt to instantiate a NEW instance with that same variable
name, the out-of-scope variable gets re-used (without passing on any
old, now definitely out-of-scope, content).
So, in short: yes, the variable will be valid. As a rule of thumb, you
can assume it becomes 'disputable' / 'invalid' when another 'call'
operation enters that scope depth level again.


> These kinds of questions are why there's not been a mainline release
> with that functionality.  The semantics are not trivial.

Twas simpler than you might think ;-)

And to answer Paolo at the same time: no extra memory cost whatsoever.
(Unless you count a few integers per variable as major storage cost --
which it isn't: the increase is maybe, hmmm.... 16 bytes per variable
instance total. And out-of-scope variable storage is re-used (when you
use the same name in an active scope again), so performance is quite
comparable to the global-only vanilla or previous GerH releases. :-)


> But your notions make me think more clearly on this again..  (as well
> as rethinking multiple-values-return capability... which is yet
> another matter, but simpler than this one at least)

Which is about dialing up the hardcoded limits on the delims. Instead
of s1start/s1end, etc., go s_start[0]/s_end[0], etc.: array notation
in C and you either statically or dynamically allocate such arrays
when parsing the script. But that's for another day.

I would get really worried when folks start reporting crossing the
100KLOC boundary in crm114 script. Because then, it's become another
JavaScript gone bursar while getting nada for the dried frogs pills
shipment delivery date.


-- 
Met vriendelijke groeten / Best regards,

Ger Hobbelt


(mail*.crm surpassed the 5 KLOC barrier already a while ago... quite
an achievement for a language which was, IMHO, a nice 'geek exercise'.
[I recall a similar thing I did way back when - the idea that you can
write an entire interpreter in yacc, no ASTs, no P-code, nothing. Just
a few lexer hacks and the yacc/bison state machine. And it worked. It
worked great, even. But in some ways it was _very_ much like the
crm114 language, emotion- and common engineering sense-wise.])



--------------------------------------------------
web:    http://www.hobbelt.com/
        http://www.hebbut.net/
mail:   [email protected]
mobile: +31-6-11 120 978
--------------------------------------------------

------------------------------------------------------------------------------
Open Source Business Conference (OSBC), March 24-25, 2009, San Francisco, CA
-OSBC tackles the biggest issue in open source: Open Sourcing the Enterprise
-Strategies to boost innovation and cut costs with open source participation
-Receive a $600 discount off the registration fee with the source code: SFAD
http://p.sf.net/sfu/XcvMzF8H
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.