From bart@cs.pdx.edu Wed, 28 Feb 2001 08:59:06 -0800
Date: Wed, 28 Feb 2001 08:59:06 -0800
From: Barton C Massey bart@cs.pdx.edu
Subject: [Nickle]A usable calculator again

Ah, but anyone with half a clue in CS knows that log_base_2
is the One Interesting Logarithm :-).  Similarly with
log_base_10 and most engineering disciplines.  But you're
probably right about naming conventions.

I'm thinking we don't need exp10 and exp2 given that we have
an exponentiation operator.  Also, I always understood "exp"
as an abbreviation for "e to the x power"... :-)

		Bart

In message <200102281016.f1SAGSp14762@ducky.net> you wrote:
> I happen to like log = log_base_e.  Anyone with half a clue in math
> knows it's One True Logarithm.  I'd vote for log10, log, and log2,
> and conversely exp10, exp, and exp2.
> 
> _______________________________________________
> Nickle mailing list
> Nickle@keithp.com
> http://keithp.com/mailman/listinfo/nickle




From keithp@keithp.com Sun, 04 Mar 2001 15:42:36 -0800
Date: Sun, 04 Mar 2001 15:42:36 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]Prototype implementation of twixt in nickle

This is a multipart MIME message.

--==_Exmh_-3164320800
Content-Type: text/plain; charset=us-ascii


Here's a prototype of how continuations and twixt blocks work together

To recap the syntax:

twixt (enter; leave) body [ else failure ]

        If 'enter' returns True, execute 'body' followed by 'leave'
        else execute 'failure'

        Ensure that any flow of control transfer from inside 'body' to
        outside the twixt passes through 'leave'

        Ensure that any flow of control transfer from outside 'twixt' to
        inside 'body' passes through 'enter'

The basic plan is:
        Jumping from 'l' to 'e'
        Find deepest common parent 'p' of 'l' and 'e' in the twixt chain
        'leave' all twixt blocks between 'l' and 'p'
        'enter' all twixt blocks between 'p' and 'e'
        Pick up execution at 'e'

One complicating factor -- 'enter' blocks may fail, at which point the
'else' clause in the twixt is executed and execution picks up from there.

First-class continuations really help out here.

-keith


--==_Exmh_-3164320800
Content-Type: text/plain ; name="twixt.5c"; charset=us-ascii
Content-Description: twixt.5c
Content-Disposition: attachment; filename="twixt.5c"

typedef struct {
    poly		    next;
    integer		    depth;
    integer(continuation c) enter;
    poly(continuation c)    leave;
} chain;

typedef struct {
    continuation    cont;
    *chain	    list;
} do_continue;

*chain	list;

do_continue	nested;

function chain_depth (*chain c)
{
    if (c)
	return c->depth;
    else
	return 0;
}

function do_jump (do_continue enter, poly value)
{
    *chain		l, e;
    *chain		l_parent, e_parent;
    continuation	c;
    integer		i;
    printf ("do_jump\n");

    l = list;
    e = enter.list;
    l_parent = l;
    e_parent = e;
    /*
     * Make both lists the same length
     */
    i = chain_depth(l) - chain_depth (e);
    if (i >= 0)
	while (i-- > 0)
	    l_parent = l_parent->next;
    else
	while (i++ < 0)
	    e_parent = e_parent->next;
    /*
     * Now find the common parent
     */
    while (l_parent != e_parent)
    {
	l_parent = l_parent->next;
	e_parent = e_parent->next;
    }
    
    /*
     * Unwind the twixt blocks in the source list
     */
    function do_leave (*chain l)
    {
	if (l != l_parent)
	{
	    if (set_jump (&c, 0) == 0)
	    {
		printf ("calling leave\n");
		l->leave (c);
	    }
	    do_leave (l->next);
	}
    }
    
    /*
     * Rewind the twixt blocks in the target list
     */
    function do_enter (*chain  e)
    {
	if (e != e_parent)
	{
	    do_enter (e->next);
	    if (set_jump (&c, 0) == 0)
	    {
		printf ("calling enter\n");
		e->enter (c);
	    }
	}
    }

    do_leave (l);
    do_enter (e);

    long_jump (enter.cont, value);
}

poly function do_set (*do_continue target, poly value)
{
    *target = do_continue { list = list };
    return set_jump (&target->cont, value);
}

function do_twixt (integer () enter, poly() leave, 
		   poly() body, poly() else_)
{
    continuation	c_enter;
    continuation	c_leave;
    continuation	c_else;
    chain		l;

    integer function f_enter (continuation c)
    {
	printf ("f_enter\n");
	if (enter())
	{
	    printf ("f_enter jumping normal\n");
	    long_jump (c, 1);
	}
	else
	{
	    printf ("f_enter jumping fail\n");
	    long_jump (c_else, 1);
	}
    }

    function f_leave (continuation c)
    {
	printf ("f_leave\n");
	leave();
	long_jump (c, 1);
    }

    if (set_jump (&c_else, 0) == 0)
    {
	if (set_jump (&c_enter, 0) == 0)
	{
	    f_enter (c_enter);
	}
	else if (set_jump (&c_leave, 0) == 0)
	{
	    chain	l;
    
	    l = chain { next = list, enter = f_enter, leave = f_leave };
	    if (list)
		l.depth = list->depth + 1;
	    else
		l.depth = 1;
	    list = &l;
	    printf ("before body\n");
	    body ();
	    printf ("after body\n");
	    f_leave (c_leave);
	}
    }
    else
    {
        printf ("else\n");
        else_();
    }
}

function body (do_continue jump)
{
    printf ("body\n");
    if (do_set (&nested, 0) == 0)
    {
	printf ("jumping\n");
	do_jump (jump, 1);
    }
    else
    {
	printf ("re-entered body\n");
	printf ("returning\n");
    }
}

integer	critical_value = 1;

function critical (do_continue jump)
{
    printf ("critical enter\n");
    do_twixt (integer func () 
	      {
		printf ("enter lambda\n");
		return critical_value;
	      },
	      func ()
	      {
		printf ("leave lambda\n");
	      },
	      func ()
	      {
		printf ("body lambda\n");
		body (jump);
	      },
	      func ()
	      {
		printf ("else lambda\n");
	      });
    
    printf ("critical done\n");
}

function start ()
{
    do_continue    c;

    printf ("start\n");
    if (do_set (&c, 0) == 0)
    {
	critical (c);
	printf ("back from critical\n");
    }
    else
    {
	printf ("back from do_jump\n");
    }
    printf ("start done\n");
}

--==_Exmh_-3164320800--






From mike@ducky.net Tue, 06 Mar 2001 21:47:25 -0800
Date: Tue, 06 Mar 2001 21:47:25 -0800
From: Mike Haertel mike@ducky.net
Subject: [Nickle]bug?

Trailing zeroes whenever I use printf() to print a rational:

> printf("%r", 1/3)
0.{3}0
     ^






From keithp@keithp.com Tue, 06 Mar 2001 22:14:54 -0800
Date: Tue, 06 Mar 2001 22:14:54 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]bug?

Around 21 o'clock on Mar 6, Mike Haertel wrote:
> Trailing zeroes whenever I use printf() to print a rational:
> 
> > printf("%r", 1/3)
> 0.{3}0
>      ^

Nope, your format isn't including a newline so you're seeing the value 
immediately followed by the return value from printf (which is 0).

Try either:

> printf ("%r", 1/3);
0.{3}> 

or

> printf ("%r\n", 1/3)
0.{3}
0
> 

-keith






From keithp@keithp.com Tue, 06 Mar 2001 23:20:02 -0800
Date: Tue, 06 Mar 2001 23:20:02 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]import and existing names

Here's a scenario:

public integer    i  = 1;
public *integer p_i  = &i;
public *integer q_i = &i;

namespace n {
    public integer    i = 2;
    public *integer p_i = &i;
}

import n;
i = 3;
n.i = 4;

What should the values of the following expressions be:

	i
	*p_i
	*q_i
	n.i
	*n.p_i

I thought (just for a minute), that importing symbols should be equivalent 
to declaring them at the outer scope; declarations now re-use existing 
global symbols when the types match so that you can redefine functions and 
have existing code use the new functions.  The old global symbol simply 
gets a new value.

This doesn't work with import -- those imported symbols may well have 
references that will still point to the nested symbol and not the existing 
global one.

The only consistent symantic I can think of is that import *and* 
redefinition *both* remove any existing symbol and replace it with the new 
one.  Pointers to the old symbol will continue to reference the old object 
while new code referencing the name will get the new object.

I'll implement this, at least for now, the alternative is chaos.

-keith







From keithp@keithp.com Wed, 07 Mar 2001 10:55:17 -0800
Date: Wed, 07 Mar 2001 10:55:17 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]printf formatting codes

Nickle currently uses a variety of different formatting codes for printing 
values, they're pretty randomly scattered through the alphabet and mean 
different things for different values.  We need to normalize their 
meaning and then fix the code.

Here's what we've got today:

	%b		base = 2
	%x		base = 16
	%X		base = 16
	%o		base = 8

int
	%c		print a character
	else		print using base

integer:
			print using base
	
rational:
	%v		numerator '/' denominator
	%f		integer '.' fraction
	%e		integer '.' fraction
	else		integer '.' fixed-part '{' repeating-part '}'

float:
	%e		mant 'E' exponent
	%f		integer '.' fraction
	else		choose between 'e' and 'f' depending on exponent

array
	%g		'[' dimensions ']' '{' contents '}'
	%v		'[' dimensions ']' '{' contents '}'
	else		contents

string
	%s		contents
	else		'"' escaped-contents '"'	

struct
	%v		'{' name = value ',' ... '}'
	else		name = value ...

It seems like there should be some general requirements:

	%v		should print in a format parsable by the lexer
	%e		should print numbers in exponential form
	%f		should print numbers in non-exponential form
	%d		should probably be like %f?  Or integer part only?
	%g		should print in a human readable format
	%c		should print a character

Should the printing function typecheck the arguments against the format 
letter?

-keith






From keithp@keithp.com Mon, 12 Mar 2001 15:32:21 -0800
Date: Mon, 12 Mar 2001 15:32:21 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]A tale of two unions

Nickle currently has two levels of type checking, static typing and pure 
polymorphism.  In other words, you either get strict compile-time 
typechecking or run-time type checking.  No middle ground for those 
programmers who waffle between the extremes.

I've implemented two different union types to try and reach a compromise.

First, take hints from existing practice.

The first attempt was to take a combination of pascal varient records and 
tagged unions from other languages.  The goal here is to allow for union 
types with compile-time typechecking within explicitly run-time 
typechecked blocks.

	typedef union {
		integer	i;
		string	s;
	} string_or_integer;

	string_or_integer	variable;

	variable.i = 10;
	variable.s = "hello world";

	union switch (variable) {
	case i:
		printf ("Integer i %g\n", variable.i);
		break;
	case s:
		printf ("String s %g\n", variable.s);
		break;
	}

While it would be nice within the union switch to refer to the variable 
without the tag, there's no effective way of restricting modifications 
to the variable that would switch the tags.  Hence, this version requires 
run-time type checking at each access.  Clipping the wings of this could
do something like:

	union switch (variable) {
	case i:
		integer temp = variable.i;
		printf ("Integer i %g\n", temp);
		break;
	case s:
		string temp = variable.s;
		printf ("String s %g\n", temp);
		break;
	}

Of course, we could hack this to make 'variable' contain said temporary 
value during the varients, but this would make 'variable' unavailable as 
an lvalue that referenced the original union.

Back to the drawing board.

It seems to me that the goal of allowing compile-time typechecking 
conflicts with the existing 'poly' type within nickle; the benefit of that 
type is that it provides easy-to-use syntax along with full run-time 
typechecking.  The second kind of unions I implemented brings a modicum of 
compile-time typechecking to a pure polymorphic world.  The goal here is 
to allow useful compile-time typechecking along with run-time typechecking 
to verify program type-correctness.

	typedef union {
		integer;
		string;
	} string_or_integer;

	string_or_integer	variable;

	variable = 10;
	variable = "hello world";

	typeswitch (variable) {
	case integer:
		printf ("integer %g\n", variable);
		break;
	case string:
		printf ("string %s\n", variable);
		break;
	}

Note that this version requires no tags; that's because the interest is in 
capturing the essence of pure polymorphism while still providing some 
typechecking; pure polymorphism allows untagged access to the value stored 
in each location with the type following the value, not the union variable.
There's no problem within the 'typeswitch' statement with capturing the 
value of the variable as all accesses must be run-time typechecked in any 
case.

It seems to me that this second version captures the usage of pure 
polymorphism within nickle while still allowing for useful typechecking to 
occur at compile time.  Without unduly restricting the 'union switch' 
semantics, the first version doesn't offer any significant compile-time or 
run-time advantages while requiring additional work and book-keeping to be 
done by the programmer.

-keith






From keithp@keithp.com Tue, 13 Mar 2001 00:32:22 -0800
Date: Tue, 13 Mar 2001 00:32:22 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]Mutex replacements in nickle

Nickle currently has two built-in synchronization types 'mutex' and 
'semaphore'.  Of course, 'semaphore' is more powerful, but the current 
'mutex' type has the nice property that only the thread which acquired the 
mutex may release it (else an exception is raised). The code also checks 
to see if the current thread holds the mutex and raises the 'deadlock' 
exception.

The obvious question is then can mutexes be implemented in terms of 
semaphores, and naturally the answer is 'yes'.  However, when I sent this 
code to Bart, he was confused by the locking order within the objects.

Here's the datatype:

	typedef union { 
	    thread; 
	    integer; 
	} nmt;
	
	public typedef struct { 
	    semaphore sem; 
	    semaphore crit; 
	    nmt t; 
	} nmutex_struct;

	public typedef *nmutex_struct nmutex;

A mutex is a reference to a data structure containing two semaphores and 
an object which refers either to the thread owning the mutex or '0' which 
indicates that no thread holds the mutex.

The 'crit' semaphore protects access to the 't' variable, the 'sem' 
variable is used to block waiting for the mutex to become available.

I wrote the acquire/release functions as:

    public integer function acquire (nmutex m)
    {
        twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
        {
            if (m->t == Thread::current())
                raise deadlock (m);
            twixt (Semaphore::signal (m->crit); Semaphore::wait (m->crit))
                Semaphore::wait (m->sem);
            m->t = Thread::current();
        }
        return 1;
    }

    public integer function release (nmutex m)
    {
        twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
        {
            if (m->t != Thread::current())
                raise notowned (m);
            m->t = 0;
            Semaphore::signal (m->sem);
        }
        return 1;
    }

The funny part is inside 'acquire' where it waits for the 'crit' 
semaphore, checks for deadlock then releases the 'crit' semaphore to wait 
for the 'sem' semaphore.  The problem is that if the code blocks on the
'sem' semaphore with the 'crit' semaphore locked, there's no way a thread 
can ever signal the 'sem' semaphore -- it must wait for the 'crit' 
semaphore first.  Classic deadlock.  So, I signal the 'crit' semaphore
before attempting to wait for the 'sem' semaphore.

I think this could be better written as:

    public function acquire (nmutex m)
    {
        twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
            if (m->t == Thread::current())
                raise deadlock (m);
        Semaphore::wait(m->sem);
        twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
            m->t = Thread::current ();
    }

    public integer function release (nmutex m)
    {
        twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
        {
            if (m->t != Thread::current())
                raise notowned (m);
            m->t = 0;
        }
        Semaphore::signal (m->sem);
        return 1;
    }

Now the semaphore usage is much clearer -- the 'sem' semaphore is always used 
with the 'crit' semaphore unlocked.

-keith






From keithp@keithp.com Thu, 15 Mar 2001 19:49:27 -0800
Date: Thu, 15 Mar 2001 19:49:27 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]Syntax for tagged unions

I've got tagged unions working again; bart has convinced me that the pain 
is only syntactic, not semantic.  So, I need to fix the syntax problems, 
but that requires some thought.  Here's what I've got currently:

Declaration:

	typedef union {
		integer		value;
		string		name;
	} token;

They look like C unions to some extent, you can access the tagged types
directly:

	token	t;
	integer	v;
	string	n;

	t.value = 10;
	v = t.value;
	t.name = "hello world";
	n = t.name;

The system does tag checking when fetching the union value, if the tag 
doesn't match, a run-time exception is generated.

You can also 'construct' a union type from a compatible value:

	t = (token.value) 12;
	if (t == (token.name) "glorf")
		printf ("Hit glorf token\n");

Finally, you can branch on the tag name using the magic 'union switch' 
statement:

	union switch (t) {
	case name:
		printf ("Name %g\n", t.name);
		break;
	case value:
		printf ("Value %g\n", t.value);
		break;
	}

Obviously, these require a bit more typing than when using the pure 
polymorphic data type 'poly', but have the advantage of doing both
type checking and tag checking at runtime.

Questions:

+ Should I try to make the tag names usable inside the 'union switch' 
  statement?  This is pure sugar; the machine must still typecheck accesses 
  as external routines (or threads) may well modify which tag is in use.
  The only way to make this more "robust" is to capture the value at the 
  switch and have the tag names refer to the captured value.  That
  makes them much less useful; any stores will be lost outside of the 
  scope.

+ Should I change the constructor syntax?  It looks a lot like a C cast,
  which is kinda what it is.

Other comments?

-keith






From bart@cs.pdx.edu Tue, 20 Mar 2001 21:04:01 -0800
Date: Tue, 20 Mar 2001 21:04:01 -0800
From: Barton C Massey bart@cs.pdx.edu
Subject: [Nickle]Multidimensional array questions

Currently, Nickle provides no way to
initialize multidimensional arrays of
unspecified dimension:
  [*]{1,2,3}
works, but
  [*,*]{{1,2},{1,2}}
is an error.

In addition, the extra braces in initializers
are optional.  These features presumably interact.

What should happen here?  My vote is for making the extra
brackets non-optional, and allowing the syntax given
above...

Also, currently, Nickle does not allow shenanigans like
  [2,*]{{1,2},{1,2}}
or even
  [*,2]{{1,2},{1,2}}
I'm perfectly fine with this "all-or-nothing" approach: an
array expression must either give all of the dimensions or
none of them.  What do others think?

		Bart Massey
		bart@cs.pdx.edu




From keithp@keithp.com Wed, 21 Mar 2001 10:40:20 -0800
Date: Wed, 21 Mar 2001 10:40:20 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]Multidimensional array questions

Around 21 o'clock on Mar 20, Barton C Massey wrote:

> Currently, Nickle provides no way to
> initialize multidimensional arrays of
> unspecified dimension:
>   [*]{1,2,3}
> works, but
>   [*,*]{{1,2},{1,2}}
> is an error.

Just a bug in the grammar.  I fixed it.

> Also, currently, Nickle does not allow shenanigans like
>   [2,*]{{1,2},{1,2}}
> or even
>   [*,2]{{1,2},{1,2}}
> I'm perfectly fine with this "all-or-nothing" approach: an
> array expression must either give all of the dimensions or
> none of them.  What do others think?

Can't do it, the parser chokes on allowing either '*' or 'expr' because 
'expr' can start with a star (left operators suck).  Pick a different 
character...

There are still problems with partial array initializers not doing the 
right thing, I'll go fix that.

-keith







From keithp@keithp.com Wed, 21 Mar 2001 10:40:20 -0800
Date: Wed, 21 Mar 2001 10:40:20 -0800
From: Keith Packard keithp@keithp.com
Subject: [Nickle]Multidimensional array questions

Around 21 o'clock on Mar 20, Barton C Massey wrote:

> Currently, Nickle provides no way to
> initialize multidimensional arrays of
> unspecified dimension:
>   [*]{1,2,3}
> works, but
>   [*,*]{{1,2},{1,2}}
> is an error.

Just a bug in the grammar.  I fixed it.

> Also, currently, Nickle does not allow shenanigans like
>   [2,*]{{1,2},{1,2}}
> or even
>   [*,2]{{1,2},{1,2}}
> I'm perfectly fine with this "all-or-nothing" approach: an
> array expression must either give all of the dimensions or
> none of them.  What do others think?

Can't do it, the parser chokes on allowing either '*' or 'expr' because 
'expr' can start with a star (left operators suck).  Pick a different 
character...

There are still problems with partial array initializers not doing the 
right thing, I'll go fix that.

-keith







