Jump to content
Site Stability Read more... ×
  • Advertisement
  • entries
    359
  • comments
    237
  • views
    190070

About this blog

Current topic is the development of a toy language to test a few syntax ideas.

Entries in this blog

 

Cliches that hold true.

Whimsy drove me from productive stuff towards profiling stuff. The maze app (last post) is indeed spending 60-70% of its time on what amounts to a single method call. It's not actually the dynamic invoke on a MethodInfo (like I suspected) but simply creating storage for the return value from a .NET method.

It shouldn't take quite that long, and I suspect that area is rife with shortcuts that could be taken to get Tangent back into the 1 order of magnitude area vs C#. Will research later. Ideally do some more productive work too; bugfixes, example apps...

Telastyn

Telastyn

 

Tangent: Maze Example

Beginning Compilation.
Imports complete. 30 types exist.

Compilation Complete.
Time Elapsed: 00:00:01.1542230

#################################################################################
#.....#...............#...#...........#.....#...............#.....#.............#
###.#.#.###########.#.#.#.#.###.#####.#.###.#######.#######.#.###.#####.#######.#
#...#.#.#...#...#...#.#.#.#.#.#.....#...#.#.......#.......#...#...#.....#.......#
#.#####.#.#.#.#.#.###.#.#.#.#.#####.#####.#######.#.#####.#####.###.#####.#######
#.......#.#...#.#.#.#.#.#.....#...#.#.....#...#...#.....#...#...#...#...#.......#
#.#######.#####.#.#.#.#######.#.#.#.###.#.#.#.#.#########.#.#.###.#####.#######.#
#.........#...#.#.#.......#.#.#.#.#.#...#...#.#.#.......#.#.#.#.....#...#.......#
###########.###.#.#######.#.#.###.#.#.#######.#.#.#####.###.#.#####.#.#.#.#######
#...#.......#...#.....#.#.#.......#.#...#.....#.#.#...#...#.#.#...#.#.#.#.#.#...#
#.#.#.#######.#######.#.#.###.#####.#.#.###.###.#.###.###.#.#.#.#.#.#.#.#.#.#.#.#
#.#...#...........#...#.#...#.#.....#.#...#.#.#...#...#...#.#...#...#.#.#.#...#.#
#.#####.###########.###.###.###.#####.###.#.#.#####.###.###.#########.###.#####.#
#.....#.#.#.........#.....#.....#...#.#...#...#.........#.........#.............#
#####.#.#.#.#############.#######.#.#.#.#######.#########.#######.#.###########.#
#.....#.#.#.#...........#.......#.#.#.#...#.....#.#.......#.......#.#...#.....#.#
#.#####.#.#.#.#########.#####.#.###.#.###.#.#####.#.#######.#########.#.#.###.#.#
#.....#.#.#.#.#...#.#...#.....#.....#...#.#...#...#...#.....#.........#...#.#.#.#
#.###.#.#.#.#.#.#.#.#.###.###########.###.###.###.#.###.###.#.#############.#.###
#.#.#.#...#.#...#.#.#...#.............#.....#...#...#...#...#...#...#.......#...#
#.#.#.###.#.#####.#.###.###.#########.#.###.###.#.###.###.#####.#.###.###.#####.#
#...#.....#.....#...#.#...#.....#...#.#.#...#...#.#...#.#.#...#.#...#.#.#...#...#
#.#####.#######.###.#.###.#######.#.###.#.###.###.#.###.#.#.#.#.#.#.#.#.###.#.###
#.#...#.#...#...#.#.....#.......#.#...#.#.#...#.#.#.#...#...#.#.#.#.#.#...#...#.#
###.#.#.#.###.#.#.#####.#######.#.###.#.###.###.#.#.#.#.#.#####.###.#.#.#.#####.#
#...#.#...#...#.#.#...#.....#...#...#...#...#.....#.#.#...#...#...#.#.#.#...#...#
#.###.#####.###.#.#.#.#####.#.###.#.###.#.###.#####.#.#####.#.###.#.#.###.#.#.#.#
#...#.......#.....#.#.......#...#.#.#...#.#.......#.#.#...#.#.....#.#.....#.#.#.#
###.###.###########.###########.###.#.###.#####.###.###.#.#.#######.#######.#.#.#
#...#...#...........#...#.......#...#...#.....#.#...#...#...#.............#.#.#.#
#.#######.###########.#.#.#######.#####.#####.#.#.#.#.#######.#.#########.#.#.###
#...#.....#.....#.#...#.#...#...#.....#.#.....#.#.#.#.#.......#.#...#...#.#.#...#
#.#.#.#####.###.#.#.###.#.#.#.###.###.#.#.#######.#.#.#.#######.#.#.#.#.#.#.#.#.#
#.#.#.......#.....#.#.#.#.#.#...#.#.#.#.#.#.......#.#.#.#.....#...#.#.#.#.#.#.#.#
#.#.#########.#####.#.#.###.###.#.#.#.#.#.#.###.#####.#.#.###.#####.#.#.###.###.#
#.#.......#...#.....#...#...#...#...#.#.#.#.#.#...#.#.#.#...#.#...#...#...#.#...#
#.#######.#.###.#####.###.###.#####.#.###.#.#.###.#.#.###.###.#.#.#######.#.#.###
#...#...#.#...#.#...#.#...#.#.......#.....#.....#.#.#.....#...#.#.#.......#.#...#
###.#.#.#.###.#.#.###.#.###.#.###################.#.#######.###.#.#.#####.#.###.#
#.#...#.#...#.#.#...#...#...#...#.......#.......#.#.........#...#.#.#.....#...#.#
#.#####.#.###.#.#.#.#####.#####.#.#.#####.#.###.#.###########.###.#.#####.###.#.#
#.......#...#.#.#.#...#.......#.#.#.......#.#.....#.#.........#...#.....#.#...#.#
#.#########.#.#.#####.#.###.###.###.#######.#####.#.#.###.#####.#######.#.#.###.#
#.#...#...#...#.....#.....#...#...#.......#...#...#.#...#.#...#.........#.#.#...#
#.#.#.###.#########.#########.###.###.#######.###.#.#.###.#.#.#####.#######.#.###
#.#.#.....#.......#.........#.#...#.#.#.....#...#.#...#...#.#.#.....#.......#...#
#.#.#####.#.#####.#########.#.#.###.#.#.###.###.#.#####.#.#.#.###.###.#########.#
#.#.....#.#...#.....#...#...#.#.#...#.#.#.......#.....#.#.#.#...#...#.#.........#
#.###.#.#####.#####.#.#.#.###.#.#.###.#.#############.#.#.#.###.###.#.#.###.###.#
#...#.#.....#.....#...#.#.#...#.#.....#.#.........#.#...#.#...#...#.#.#...#...#.#
###.#.#####.#####.#####.#.#.###.#######.#.#######.#.#####.###.###.###.#######.###
#.#.#.....#.....#.#...#.#.#.............#...#.....#.#...#.#...#.#...#.#.....#...#
#.#.#########.###.#.#.#.#.#########.#######.#.#####.#.#.#.#.###.###.#.#.###.###.#
#.#...........#...#.#.#.#.....#...#.#.....#.#.#.....#.#...#.#.....#...#...#.....#
#.###########.#.###.#.#.#####.#.###.#.###.#.#.#.#####.#####.#####.#####.#.#.#####
#...#.........#.....#.#.....#.#...#.#...#.#.#.#...........#.......#...#.#.#.#...#
#.###.###############.#####.#.#.#.#.###.#.#.#.#######.###########.#.#.###.###.#.#
#.#...#.....#...#.#...#...#.#.#.#...#...#...#.#.....#.#.......#...#.#...#.....#.#
#.#.###.#.#.#.#.#.#.#####.#.#.#######.#######.#.###.###.#.#####.###.###.#######.#
#.#...#.#.#...#.#.#...#...#...#.....#.#.....#...#.#...#.#.....#.#...#.#...#...#.#
#.###.###.#####.#.###.#.#####.#.###.#.#.###.#####.###.#.#####.#.#.###.###.#.#.#.#
#...#.....#...#.....#.#.#...#.#...#...#.#.#.........#...#.#...#.#...#.......#.#.#
#.#########.#####.###.#.#.#.#.###.#####.#.#.#######.#####.#.###.#.#.#.#######.#.#
#.......#...#...#.#...#...#...#...#...#.#.#...#...#...#...#.....#.#.#.#.....#.#.#
#.###.###.#.#.#.#.#.###########.#####.#.#.###.#.#.###.###.#######.#.###.###.#.#.#
#.#...#...#...#.#.#.....#.......#.....#.#...#.#.#...#...#.....#...#.....#...#.#.#
#.#.###.#######.#.#####.#.#########.#.#.#.###.#####.###.#.#.#.###########.###.#.#
#.#.....#...#...#.#.#...#.#...#.....#.#.#...#.#...#.....#.#.#.....#.......#.#.#.#
#.#######.#.#.###.#.#.###.#.#.#.#####.#.#.#.#.#.#.#####.#.#.#####.#.#######.#.#.#
#.#.......#.#.#...#.#.....#.#.#...#...#.#.#.#...#.....#.#.#.....#...#.#.......#.#
#.#.#######.#.#.###.#######.#.###.#.###.###.#########.#.#######.#####.#.#######.#
#...#...#...#...#.......#...#.....#.#.............#...#.........#...#...#.#.....#
#####.###.#######.#####.#.#.#########.###########.#.#############.###.###.#.###.#
#...#...#.#.....#...#.#.#.#...........#.........#.#...........#.......#.....#.#.#
#.###.#.#.#.###.###.#.#.###.###########.#######.#########.###.#.#######.#####.#.#
#...#.#.#...#.#.#...#.#...#.#.....#.....#.#...#.#.......#.#...#.#.............#.#
#.#.#.#.#####.#.#.###.###.#.#####.#.#####.#.#.#.#.#####.###.###.#.#############.#
#.#...#.......#...#.......#.......#.....#...#...#...#.#...#.#...#...#.....#.....#
#.#######.#########.###################.#.#########.#.###.#.#.#######.###.#.###.#
#.......#...............................#...........#.......#.........#.....#...#
#################################################################################

Execution time: 00:00:46.7645850
MethodGroupInstance::Select: 188558 - 00:00:00.6151950




// no enums yet... (bug #9)
public goose class PathState{}
public goose class Wall: PathState{}
public goose class Clear: PathState{}

public Wall wall = new Wall;
public Clear clear = new Clear;

public goose class TileState{}
public goose class Unvisited: TileState{}
public goose class Visited: TileState{}

public Unvisited unvisited = new Unvisited;
public Visited visited = new Visited;

public goose class CardinalDirection{}

public static CardinalDirection direction from code (int DirectionCode){
if(DirectionCode == 0){
return(north);
}else if(DirectionCode == 1){
return(east);
}else if(DirectionCode == 2){
return(south);
}else if(DirectionCode == 3){
return(west);
}else{
return(null);
}
}

public static CardinalDirection random direction{
return(direction from code rng.Next(0,4));
}

public goose class East: CardinalDirection{}
public goose class North: CardinalDirection{}
public goose class West: CardinalDirection{}
public goose class South: CardinalDirection{}

public East east = new East;
public North north = new North;
public West west = new West;
public South south = new South;


public Random rng = new Random;

public class Coordinate{
public int x;
public int y;

public bool this exists in (Map map){
if(x 0 || map.Tiles.Count return(false);
}
if(y 0 || map.Tiles[x].Count return(false);
}
return(true);
}

// Should be this methods so can do 'Coord move north' (bug #15)
public Coordinate move (CardinalDirection direction){return(null);}
public Coordinate move (North direction){
local Coordinate rtn = new Coordinate;

rtn.x = x;
rtn.y = y - 1;
return(rtn);
}

public Coordinate move (East direction){
local Coordinate rtn = new Coordinate;

rtn.x = x + 1;
rtn.y = y;
return(rtn);
}

public Coordinate move (South direction){
local Coordinate rtn = new Coordinate;

rtn.x = x;
rtn.y = y + 1;
return(rtn);
}

public Coordinate move (West direction){
local Coordinate rtn = new Coordinate;

rtn.x = x - 1;
rtn.y = y;
return(rtn);
}
}

public class Tile{
public PathState EastPath = wall;
public PathState SouthPath = wall;

public TileState Status = unvisited;
}

public class Map{
public int maxX;
public int maxY;

public List> Tiles = new List>;

public void Initialize(){

Tiles.Clear();

// no for loops yet... (bug #11)
local int x;
local int y;
do{
y = 0;
Tiles.Add(new List);
do{
Tiles[x].Add(new Tile);
y=y+1;
}while(y x=x+1;
}while(x }

}

public static void display( Map TargetMaze ){
local int x;
local int y;

x = 0;
print "#";
do{
print "##";
x = x + 1;
}while(x
print " \r\n";

x = 0;
y = 0;
do{
print "#";
x = 0;
do{
print ".";
if( TargetMaze.Tiles[x][y].EastPath == wall ){
print "#";
}else{
print ".";
}
x = x + 1;
}while(x
print " \r\n";

x = 0;
print "#";
do{
if( TargetMaze.Tiles[x][y].SouthPath == wall ){
print "#";
}else{
print ".";
}
print "#";
x = x+1;
}while(x
print " \r\n";
y = y+1;
}while( y
}

public static bool done in (Map workMap) at (Coordinate Cursor){
local Coordinate workCoordinate = new Coordinate;

workCoordinate = Cursor.move north;
if(workCoordinate exists in workMap && workMap.Tiles[workCoordinate.x][workCoordinate.y].Status == unvisited){
return(false);
}

workCoordinate = Cursor.move east;
if(workCoordinate exists in workMap && workMap.Tiles[workCoordinate.x][workCoordinate.y].Status == unvisited){
return(false);
}

workCoordinate = Cursor.move south;
if(workCoordinate exists in workMap && workMap.Tiles[workCoordinate.x][workCoordinate.y].Status == unvisited){
return(false);
}

workCoordinate = Cursor.move west;
if(workCoordinate exists in workMap && workMap.Tiles[workCoordinate.x][workCoordinate.y].Status == unvisited){
return(false);
}

return(true);
}


public static void MazeStep( Map workMap, Coordinate Cursor){

workMap.Tiles[Cursor.x][Cursor.y].Status = visited;

// Not declared in the while loop due to bug #14.
local CardinalDirection RandomDirection;
local Coordinate NewCursor;

while( !done in workMap at Cursor ){


do{
RandomDirection = random direction;
NewCursor = Cursor.move RandomDirection;

}while( !(NewCursor exists in workMap &&
workMap.Tiles[NewCursor.x][NewCursor.y].Status == unvisited));

if( RandomDirection == east ){
workMap.Tiles[Cursor.x][Cursor.y].EastPath = clear;
}else if(RandomDirection == south){
workMap.Tiles[Cursor.x][Cursor.y].SouthPath = clear;
}else if(RandomDirection == west){
workMap.Tiles[NewCursor.x][NewCursor.y].EastPath = clear;
}else if(RandomDirection == north){
workMap.Tiles[NewCursor.x][NewCursor.y].SouthPath = clear;
}else{
print "!!! Unknown random direction?!?";
}

MazeStep(workMap,NewCursor);
}

}

public static Map create (int xsize) by (int ysize) maze{

local Map rtn = new Map;

rtn.maxX = xsize;
rtn.maxY = ysize;

rtn.Initialize();

local Coordinate Cursor = new Coordinate;

Cursor.x = rng.Next(0,xsize);
Cursor.y = rng.Next(0,ysize);

MazeStep(rtn,Cursor);

return(rtn);
}

public static void main(){

display (create 40 by 40 maze);
}






And there you go; slightly different than the prelim code. Worked through about 8 non-trivial bugs, 1 super bug, and worked around ~2 other new ones but it's there and works. It is dog slow though (my C# workshop app takes 0.1s to do the same thing vs 46.7s for the Tangent app). A little surprising since I expected that the compilation time would be the big problem with the growth in app size. But that's fine. You can profile working code and you can add optimizations to a compiler.

Now to think of another example app to shake more bugs out of this thing. Something that touches function passing and properties and class stuff more. Alas, it'll probably be 2-3 more test cycles before release. Too bad since I need to get more hands and eyes for feedback.

Telastyn

Telastyn

 

The great 2008 bug hunt.

Sorry folks, no maze output tonight. I spent the night (and half of last night) hunting one bug. The bug manifested itself in double-lists: List> but only when Tangent types were involved. List> worked fine. The symptom was that the second indexer call would crap out in method group invoke saying no actual method group matched the runtime type.

Which should be impossible since the thing is checked at compile time and there's no type coercion yet. So I step through code and at the method group selection bit, the indexer is shown as Tangent.Lang.Object get_Item(int); and the parsed type was Tile get_Item(int) as it should've been. Kinda makes sense. I use the basic objects when tangent objects need to go to .NET land.

But the types never see that. The only way the indexer would ever get that is if I type parameterized List with Object explicitly. So I looked for that. And looked. Not only didn't I find it, but everywhere I saw List> I saw the correct typings. I stepped through the VM as the instance was created; as the inner list was created and added. Stepped through the passing of individual parameters between contexts and everything looked kosher.

I went back to the binding, where I import .NET types, where the methods are re-tied to types when bound... I double check that I'm not somehow polluting the cache with List and that's being erroneously returned... Nothing. Worse than nothing; nothing that even looked to be a likely candidate. No type after compilation even referenced Tangent.Lang.Object.

So I stepped through every instantiation to see where an object got that a type with the indexer's bad return value. That thankfully proved successful and let me trace the thing back. ~6 month old dormant code turned out to be the problem. When a .NET method is called, the result is stuffed into a return value field with a TangentType representation. Normally, the TangentType is pulled from the Tangent definition for the called method. Even for .NET invocations.

Abnormal is triggered when the return type was not a .NET type. For List> the first indexer's return type should be List but is technically a bound generic reference. It was not seen as a proper .NET type by the old code. The abnormal path was very helpful. It would reflect into the returned value's type and construct a TangentType for it. In this case, it saw List and helpfully used that as the returned instance's type. When the later indexer ran, the mis-typed instance didn't mesh with what it expected and exceptions got tossed.


And of course, there's still more layers of bug preventing the maze app from running. It compiles though, and last post's example code is still looking good. Just bugs in the compiler/VM. Tomorrow maybe, Friday more likely.

Telastyn

Telastyn

 

Tangent: Prelim Maze Gen

Had some time tonight (and more importantly, some motivation) and worked through the maze gen. It doesn't quite run yet. Fixed two bugs, one with default initializers and complex types (this-method instances), another where a returned instance's indexers were ignored. I've still got an elusive bug around either .NET imports or indexers that causes the first indexer call of List> to not work. But time has run out and my brains have fled. I shall find it tomorrow.

In the meantime, code that probably mostly should work for a simple maze gen:


// no enums yet...
public goose class PathState{}
public goose class Wall: PathState{}
public goose class Clear: PathState{}

public Wall wall = new Wall;
public Clear clear = new Clear;

public goose class TileState{}
public goose class Unvisited: TileState{}
public goose class Visited: TileState{}

public Unvisited unvisited = new Unvisited;
public Visited visited = new Visited;

public goose class CardinalDirection{}

public static CardinalDirection direction from int (int DirectionCode){
if(DirectionCode == 0){
return(north);
}else if(DirectionCode == 1){
return(east);
}else if(DirectionCode == 2){
return(south);
}else if(DirectionCode == 3){
return(west);
}else{
return(null);
}
}

public static CardinalDirection random direction{
return(direction from int rng.Next(0,4));
}

public goose class East: CardinalDirection{}
public goose class North: CardinalDirection{}
public goose class West: CardinalDirection{}
public goose class South: CardinalDirection{}

public East east = new East;
public North north = new North;
public West west = new West;
public South south = new South;


public Random rng = new Random;

public class Coordinate{
public int x;
public int y;

public bool this exists in (Map map){
if(x 0 || map.Tiles.Count return(false);
}
if(y 0 || map.Tiles[x].Count return(false);
}
return(true);
}

public Coordinate this move (CardinalDirection direction){return(null);}
public Coordinate this move (North direction){
local Coordinate rtn = new Coordinate;

rtn.x = this.x;
rtn.y = this.y - 1;
return(rtn);
}

public Coordinate this move (East direction){
local Coordinate rtn = new Coordinate;

rtn.x = this.x + 1;
rtn.y = this.y;
return(rtn);
}

public Coordinate this move (South direction){
local Coordinate rtn = new Coordinate;

rtn.x = this.x;
rtn.y = this.y + 1;
return(rtn);
}

public Coordinate this move (West direction){
local Coordinate rtn = new Coordinate;

rtn.x = this.x - 1;
rtn.y = this.y;
return(rtn);
}
}

public class Tile{
public PathState EastPath = wall;
public PathState SouthPath = wall;

public TileState Status = unvisited;
}

public class Map{
public int maxX;
public int maxY;

public List> Tiles = new List>;

public void Initialize(){

Tiles.Clear();

// no for loops yet...
local int x;
local int y;
do{
Tiles.Add(new List);
do{
Tiles[x].Add(new Tile);
y=y+1;
}while(y x=x+1;
}while(x }

}

public static void display( Map TargetMaze ){
local int x;
local int y;

x = 0;
print "#";
do{
print "##";
}while(x
print " \r\n";

x = 0;
y = 0;
do{
print "#";
do{
print ".";
if( TargetMaze.Tiles[x][y].EastPath == wall ){
print "#";
}else{
print ".";
}
x = x + 1;
}while(x
print " \r\n";

x = 0;
print "#";
do{
if( TargetMaze.Tiles[x][y].SouthPath == wall ){
print "#";
}else{
print ".";
}
print "#";
x = x+1;
}while(x
print " \r\n";
y = y+1;
}while( y
}

public static bool done in (Map workMap) at (Coordinate Cursor){
local Coordinate workCoordinate = new Coordinate;

workCoordinate = Cursor move north;
if(workCoordinate exists in workMap && workMap.Tiles[workCoordinate.x][workCoordinate.y].Status == unvisited){
return(false);
}

workCoordinate = Cursor move east;
if(workCoordinate exists in workMap && workMap.Tiles[workCoordinate.x][workCoordinate.y].Status == unvisited){
return(false);
}

workCoordinate = Cursor move south;
if(workCoordinate exists in workMap && workMap.Tiles[workCoordinate.x][workCoordinate.y].Status == unvisited){
return(false);
}

workCoordinate = Cursor move west;
if(workCoordinate exists in workMap && workMap.Tiles[workCoordinate.x][workCoordinate.y].Status == unvisited){
return(false);
}

return(true);
}

public static void MazeStep( Map workMap, Coordinate Cursor, List Path){

workMap.Tiles[Cursor.x][Cursor.y].Status = visited;
Path.Add(Cursor);


while( !done in workMap at Cursor ){
local CardinalDirection RandomDirection;
local Coordinate NewCursor;

do{
RandomDirection = random direction;
NewCursor = Cursor move RandomDirection;

}while( !( NewCursor exists in workMap &&
workMap[NewCursor.x][NewCursor.y].Status == unvisited));

if( RandomDirection == east ){
workMap.Tiles[Cursor.x][Cursor.y].EastPath = clear;
}else if(RandomDirection == south){
workMap.Tiles[Cursor.x][Cursor.y].SouthPath = clear;
}else if(RandomDirection == west){
workMap.Tiles[NewCursor.x][NewCursor.y].EastPath = clear;
}else if(RandomDirection == north){
workMap.Tiles[NewCursor.x][NewCursor.y].SouthPath = clear;
}else{
print "!!! Unknown random direction?!?";
}

MazeStep(workMap,NewCursor,Path);
}

Path.RemoveAt(Path.Count - 1);
}

public static Map create (int xsize) by (int ysize) maze{
local Map rtn = new Map;
rtn.maxX = xsize;
rtn.maxY = ysize;

rtn.Initialize();

local Coordinate Cursor = new Coordinate;

Cursor.x = rng.Next(0,xsize);
Cursor.y = rng.Next(0,ysize);

MazeStep(rtn,Cursor,new List);

return(rtn);
}

public static void main(){
display (create 40 by 40 maze);
}








For loops don't exist yet, nor do enums, nor does the increment operators, nor does class static methods. Plus the language currently doesn't respect private/public/etc, but does include that info for type compares (so everything is public and there's no property use). And Dictionarys are dubious yet.

Included are some examples of this-method use, explicit identifiers, and polymorphism on parameters. Feedback encouraged, even if (/especially if) it's less than complementary. Some of the naming and structure conventions are a little varied. Still trying to get a feel for writing in the new language since making it and using it are not really complementary at all. But now sleep. Tomorrow debugging and hopefully some final code and some output.

Telastyn

Telastyn

 

Tangent: Bug Update

Tiny update. The bug from the last post has been found and killed. The problem wasn't with nested type parameterization (that works fine). The problem was that when Tangent instances go into .NET, they go as the underlying .NET instance of all Tangent instances. Tangent then handles the type safety. When I re-import the List so I can call it's methods, I need to make a Tangent Type for foo (Tangent Object). Since there's certain stuff not implemented, I fail out if the type doesn't have a public default constructor. Tangent Object did not, so the re-import failed to import all the members of List that use T. Oops.

But roadblock cleared. I have Wednesday targeted towards getting the maze app completed. Hopefully baring too many more roadblocks I'll have a nice, bigger example app in Tangent to show off then.

Note: Tangent will support importing of types without default constructors, I just haven't figured out how that's going to work at time of writing.

Telastyn

Telastyn

 

Slow week.

Not much to report this week. The explicit identifier stuff took a lot of energy and was followed up by hectic real work. Plus I'm at a point where pretty much all of the experimental sort of Tangent features are implemented, leaving the relatively mundane tasks (implementing generic declarations, exceptions, statics, lock/using/yield, events, public/private/etc) that I've skipped over. I'm not really good with that middle ground of mundane but not trivial code monkey work.

I'm aiming to do the maze chapter of the C# workshop as a target app in Tangent to see what's missing and show off language features. It's working well. Five minutes and I found 2 bugs, one of which turned into a blocker of the app (List> runs into issues during type parameterization).

Hopefully more to come as the week goes along and the bugs are shaken out of the system. Ideally I'll have another public release after the maze app and maybe one other shakedown.

Telastyn

Telastyn

 

Tangent: Phrases

Hah! I finally got around to finishing off the phrase declarations. The implementation is a single embaressingly horrid 13 parameter, 800 line, procedural, recursive method. It's been a while since I've faced a problem so frustrating or beyond my grasp that I diverted so much energy from basic code structure to it. Even the type and order of operations inferring didn't.

Anyways, on to the feature:

Before the change, Tangent method declarations were in the form:
return-type identifier() [block|semi-colon]

(with generics and constraints left to be implemented) After the change, method declarations are of the form:
return-type identifier [()|identifier]+ [block|semi-colon]

Or to sum up for those who might not know regex/bnf symbols, instead of a number of traditional C-like method parameters in parens, you can provide any number of similar parens or identifiers as the parameter list to a method.

Example code:
[source lang = "C#"]
public void bar for (bool really){

if(really){
print "Yeah, I can cover everyone!\r\n";
}else{
print "Eh... put it on the card.\r\n";
}
}

public void bar for everyone{ print "Everyone!!!\r\n"; }
public void bar me{ print "me.\r\n"; }
public void bar you{ print "you.\r\n"; }
public void bar (bool eh){
if(eh){
print "EH!\r\n";
}else{
print "enh.\r\n";
}
}


public static void main(){
bar me;
bar you;
bar true;
bar(false);
bar for everyone;
bar for true;
bar for false;
}






me.
you.
EH!
enh.
Everyone!!!
Yeah, I can cover everyone!
Eh... put it on the card.


for isn't a keyword (yet) so is an acceptable identifier in this context. The declarations are essentially sugar for more complex interactions. The first example above could be defined as:


public class intermediary{
public void this(bool really){
if(really){
print "Yeah, I can cover everyone!\r\n";
}else{
print "Eh... put it on the card.\r\n";
}
}
}

public intermediary bar for{ return(new intermediary); }





Which of course becomes a lot more complicated when the intermediary needs to store bound params and determine the final baked method. Since this sort of thing seemed like something that people would want to do to customize the syntax towards a particular domain, and the process of creating these intermediary types was very mechanical... I sugar'd it. The compiler itself generates these stupid little intermediary types and does the parameter manipulation for you.

The addition of simple identifiers as parameters is to allow call-location info to the parameters when there becomes a number of them. Also it will allow a little more verbose/natural syntaxes with smaller effort. And as an added bonus, they can serve as an extra token to distinguish between methods in a group which take the same number and type of parameters.


I personally think that it's a useful addition, and something that is a natural extension to some of the other weird stuff that sets Tangent apart. My coworker thinks it's a mistake; a tool too easily used for obfuscation in a language already leaning towards the ambiguous. Experience using the language will tell I think. Now that this feature is done, I can hopefully go back and polish properties and imports and type binding... Actually use or at least release for others to get some of that feedback.

Telastyn

Telastyn

 

Mini update.

Little work tonight. I have method picking based on explicit identifier and combined with a standard parameter working now. There's no priority for the identifier versions yet. I also need to make null not trigger the explicit identifier methods [edit: done]. Mostly though I'm still running into mental roadblocks about how to implement the intermediary types for the phrase definitions... Anyways, test source:


public class foo{
public void bar me{ print "me.\r\n"; }
public void bar you{ print "you.\r\n"; }
public void bar (bool eh){
if(eh){
print "EH!\r\n";
}else{
print "enh.\r\n";
}
}
}

public static void main(){
(new foo).bar me;
(new foo).bar you;
(new foo).bar true;
(new foo).bar(false);
}





me.
you.
EH!
enh.

Telastyn

Telastyn

 

Tangent: Explicit Identifier Arguments

One of the main design focuses for Tangent as it stands now is to better allow Domain Specific Language (hereafter DSL) tendencies. Many of my primary motivations come from game programming where scripting languages are now ubiquitous. Even in the business world, there's been a proliferation of object modeling if not outright new DSLs made to try and take general programming languages and move them closer to the problems that programmers face.

And at least naively, it makes sense. The more naturally you can describe your solution (or problem depending on the language), the better that description will tend to be. Speaking the lingo of the domain you're working in helps that immensely. That's one of the reasons motivating Tangent's user definable operators (similar to Scala in that regard).


I talked earlier about including method definitions that are like phrases. These too tend around this motivator. In summary:
Orc smash Knight;

is acceptable Tangent syntax, presuming the smash operator is defined and suitable Orc and Knight instances are defined and in scope and everything. To make a preposition to that operation though:
Orc smash Knight with Boulder;

The original operation would need to return a method which takes the preposition (however you define it). Which is awkward, and kinda unreadable. So a better syntax might be:

public operator void smash(Actor Subject, GameObject Target);
public operator void smash(Actor Subject, GameObject Target) (WeaponPreposition WeaponSuffix);
// or
public operator void smash(Actor Subject, GameObject Target) with (GameObject Weapon);


The final option seems (to me) to be the best in cases where the preposition can't be nicely abstracted. Too often I see method overloads (or simply ill-formed methods) where what the parameters do is inscrutable. Or the names suddenly become 'SmashTargetWithWeapon' which is informative, but (sometimes) overkill and not exceptionally readable to humans. Allowing explicit identifiers ('with' in the above example) to act as a overload selector or simply a descriptor to a parameter, people should be able to write more legible code. Ideally it'll be more descriptive too since some of the annoyances to being descriptive are removed and it's not difficult to write (like returning a intermediary method would be). And they allow the programmer to steer the syntax towards their domain.


The first step in getting that working is support for the explicit identifiers. The generation of intermediary types will come second. So I spent tonight implementing that general concept into Tangent. It touches a lot more things than I'd have thought, and was a little tricky to do in such a way that method picking still worked nicely, and the identifier tokens weren't picked up by the Any type. A bit still doesn't work. Binary operators don't recognize them, their precedence over alternatives during ambiguity isn't there, they don't actually work in method picking yet and probably a few other weird things.

But the example code for the evening:

public class foo{
public void bar me{
print "moo.";
}
}

public static void main(){
(new foo).bar me; // moo.
}





Next will come finishing these guys off, adding the intermediary types, and debugging. Then hopefully my whimsy shall turn more practical so I can do bug hunting and polish for all these damned features.

Telastyn

Telastyn

 

Tangent: Indexers

Added basic support for indexers tonight. Indexers suck. They're lengthy, they're weird, and despite their limited utility they're pretty essential for that utility. And most things still seem to be broken. Prolly the type parameterization stuff. Whatever. I can now create, add to, retrieve from, and remove from Lists of elemental types. Tangent types, passing them about, Dictionaries and general polish will come tomorrow.

Telastyn

Telastyn

 

Tangent: Type Parameterization, Part 1

It's been a slow two weeks with Disgaea 3 and American Labor Day occupying a lot of my time. Such layoffs seem to help though; they provide time to design. Time to scheme up good solutions for things. Like this. The problem is directly from Tangent internals. A type holds a number of definitions. Binding a type parameter changes that type into a new type. Dot Net methods are held as method info bits (at the moment) for invocation. Their grip on their owners or the owners is slim. And for concrete types, this works well. Generic Types... less good.


Anyways, I devised a solution that is less icky than the alternative, but still substantially different from what I was aiming for. I essentially took the code to infer generic method parameters and use it to line up the unbound method infos with their bound brethren.

Combined with the work I've been doing on .NET imports, and this code now works:


public static void main(){
local Listint> lint = new Listint>;

lint.Add(42);

print lint.Count;
// lint.Add("moo"); // Error.
}






Nicely imported (right now hard-coded imports until the compiler UI is improved and 'using ' works), as type-safe as everything else, and shouldn't break if used with other .NET imported stuff. Not quite usable yet though. There's a minor bug passing Tangent types as the type param (but it will be supported). Indexers don't work and enumerators aren't imported nicely yet due to the 1.0/2.0 clusterfuck around them... So you can't actually get anything out of the container.

So imports mostly work, and type parameterization pretty much works; needs polish. Oh, and you can't define generic types in Tangent yet. So that now goes into the queue to replace what I did get done.

The immediate queue is polish for the imports/parameterization, fix property = property behavior, some semi-substantial test app now that containers work, indexers, generic type definitions, 'phrase' definitions (see 2 posts down), for/foreach loops... in some order as my whimsy deems fit and Disgaea allows.

Telastyn

Telastyn

 

Tangent Tidbits.

Not much done over the long labor day weekend. Social obligations brought me away from the internet. Not so bad with a good book (American Gods) if it's infrequent.

Tangent work tonight was some updates to the .NET importer and certain bits of the type system to support importing generic types such that the objects still interoperated with .NET where only .NET types were involved, but supported Tangent types for the generic params. Plus getting Tangent's partial application of type params working beside .NET's required full application. Pretty much entirely aimed at getting lists and dictionaries.

Most things seem to be there now. The types are imported nicely, the generic params setup for the equivalent Tangent Type. I'm not exactly a whiz at .NET reflection, so I'm feeling my way into discovering the vagaries of generic parameters and the such. Plus some of the bits and oddities surrounding enumerators and explicit interface implementations. Those things are keeping the useful bits of Lists from being imported. Once that's done, I'll need to add generic type binding to Tangent syntax; and then foreach.

I've got 3 choices I figure for Tangent foreach.

1- Make it just like C# foreach.
2- Include an implicit 'where typeof(x) == foo':


List Inventory;
//...
foreach(Bottle entry in Inventory){
// implicitly no-op on Inventory entries which are not Bottles
quaff entry;
}




3- Implement primitive matching (essentially sugar for anonymous method groups) and make foreach a specialized use for it:


//
// syntax designed on the fly!
//
List Inventory;
//...
foreach(entry in Inventory){
match (entry)=>void{
(Bottle)=>{ quaff entry; }
(Garbage)=>{ drop entry; }
(NuclearWeapon)=>{ activate entry; }
(Any)=>{ }
}

}




Not quite sure yet. #3 can mostly be implemented on-top of 1 or 2 if need be. I'm wary of treading on functional niceties at this point. Not familiar enough to know many of the nuances, quirks and expectations of them. I'm not too worried that the naive use cases aren't sufficient enough for getting stuff done, but I'm not too confidant that I'd implement things sufficiently for those who are aware of the quirks.

Telastyn

Telastyn

 

Tangent: Properties

A week long vacation that is more work than relaxation sucks. 5 extra 3-day weekends? Infinitely better. Today is one of those random bonus days. In addition to squishing that one bug (see below) I wanted to get properties working. And lo, they work (at least for the simple case):


public class foo{
private int internalX;

public int x{
get{
return(internalX);
}
set{
internalX = value;
}
}
}

public static void main(){
local foo bar = new foo;

bar.x = 42;
print bar.x;
}




They should follow the standard C# syntax. Getter only works, setter only untested. Property = Property is untested, but I tried to make sure that it'd work nicely. Invoking the getter/setter using the standard syntax currently works (I need to disable it). And as usual, there's probably some corner cases that don't work quite yet (at least as expected).

A property in Tangent isn't much more than a normal method/method group. Just an extra token to signify that they should be sugared into property syntax. Thus they will likely be assignable to delegates that match the syntax, and passed around as methods/groups can. Declaring the property type as a parameter though will require generics, so isn't available at time of writing.

After Disgaea 3, I'll probably aim for .NET imports, but I might look into something I've been meaning to implement, but haven't quite worked out the details:

Tangent allows you to just stick identifiers next to one another. So something like this is kosher:

Ogre1 smash Knight1

Nice and simple. smash is an infix operator that takes actors. But something like this should be fine too:

Ogre1 move towards Knight1

move would be an infix operator for an actor and some intermediary (towards ) or takes towards and returns an intermediary infix operator.

This sort of thing I think will become (or should become) pretty common in Tangent code. Building it though will involve a number of intermediary types which will be tedious to implement. I should be able to mechanically (and anonymously) implement them. The method declaration would then become something like:

public operator void move towards(Actor Subject, Actor Target);
or (if you wanted a more 2nd-person feel)
public void move (Actor Subject) towards (GameObject Target);

The second would mechanically create three methods.

intermediary0 move (Actor);
intermediary1 towards (GameObject);
void intermediary0 intermediary1

(or)

intermediary0 move (Actor);
intermediary1 intermediary0 towards;
void intermediary1 (GameObject);


The second probably being more consistent. Just need to take my time and wrap my head around the syntax and implementation. One of those things that would probably be a lot easier if I had a theory of computation class under my belt...

Ah well. Properties. w00t.

Telastyn

Telastyn

 

Tangent: Buggery

Ran into an interesting bug over the weekend, and fixed today. The following code was in error:


public class foo{
public operator delegate int op(int x, int y);
}

public static void main(){
local foo x = new foo;

x.op = (int x, int y) => int{return(x+y);};

print 2 x.op 6;
}



The error was Unknown identifier 'lhs'

Sure, no line numbers sucks, but there's no lhs identifier in here. In debug mode, I could find the source of the error and it resolved to my built-in minus operator (which takes lhs/rhs as parameters). It thought that its parameter names were x/y. So it kinda appears to be that the lambda is leaking somehow and overwriting the names. Or that their shared base class (BinaryOperator) was getting its names set and then propagating them improperly.

Turned out to be a little more simple than that. Parameter names are a little bit of a hack at this point. A method can have an extra member called ParameterNames which is a tuple of voids that have their member-names set accordingly. Thus the lambda would be:


Parameters: int "0"/int "1"
ParameterNames: void "x"/void "y"


Ugly, and even I don't remember quite why it's setup like this. Anyways, the problem was caused by caching of the tuple results. If you ask for int,int you don't get a new type that is int tuple int you get the one and only type int tuple int. The member names though don't matter for tuple type equality. So when the lambda asked for void tuple void to set the parameter names, it got the one and only void tuple void (which minus was using for its parameter names) and overwrote the name values.

So now a check is done to prevent caching when one of the members in the tuple is void.

Telastyn

Telastyn

 

Tangent: Lambda Expressions

Daerax asked if Tangent was going to support local functions. It seems he meant anonymous delegate things, so yes. They're very useful for just little tidbits of glue code (because there's no 'bind' equivalent in C#), and pretty vital in providing snippets as strategies. Since Tangent is making an increased emphasis on functions over objects, they gain that much more importance.

I went with C# lambda style syntax, since that does tend to be cleaner and a little less ambiguous for the parser than alternatives. Unfortunately, Tangent can't (nicely) support the type inference C# allows on params and on return type. So the Tangent syntax is a bit more verbose:

( method-parameter-list ) => return-type-expression block

It kinda sucks, because the verbosity might push people away from its use. And it will likely turn away functional language advocates who like things nice and succinct. I dislike succinctness in languages, but x => x + 1; is pretty damned clear.

Making the lambdas work required only a few changes. The syntax itself is constructed of bits already used elsewhere. When the lambda's block is compiled, the lambda's scope is simply added to the stack of scopes for name resolution. The name resolution got an added snippet of code to tack on a 'use the outer scope' placeholder (for locals/params/member vars in the outer method). That placeholder then becomes a VM opcode (access outer-scope) during compilation. The lambda itself is compiled into its own VM opcode that creates the method instance and binds the outer scope (params, calling-instance, locals) with it.

This should emulate C#'s closure behavior (and it's a bug if it doesn't), such as weird stuff like this:

void foo(){
int x = 0;
someDelegate = ()=>{Console.WriteLine(x);};

x = 1000;
}

foo();
someDelegate(); // 1000!





And now for some (mildly ugly) test-code:


public class foo{
public delegate bool Conditional(int x);
public delegate void Action(int x);

public void Process(int x){
if( Conditional(x) ){
Action(x);
}
}
}

public static void main(){
local foo A = new foo;
local foo B = new foo;


A.Conditional = (int x)=>bool{
return(x10);
};

A.Action = (int x)=>void{
print "A:" x " \r\n";
};

B.Conditional = (int x)=>bool{
return(x==42);
};

B.Action = (int x)=>void{
print "B!\r\n";
};


A.Process(4);
A.Process(42);
B.Process(4);
B.Process(42);
}

// A:4
// B!






Binary parameter lambdas can be used as operators (not recommended) or stored in an operator delegate (more recommended). I will be providing a mechanism for combining them (and other arbitrary methods) into a method group, which should yield the ability to do stuff akin to pattern matching found in functional languages (as well as mildly nifty polymorphism on event-handling).

Next in the queue should be Properties. Though Disgaea 3 is coming out next week. Productivity that doesn't involve joyously evil tactical role playing is likely to decline precipitously.

Telastyn

Telastyn

 

Tangent: Delegates

Had a little bit of time and worked a bit on delegates tonight. Turns out they weren't terribly difficult due to some good planning on my part, and because a lot of the stuff was already there. I still need to make normal methods not assignable, and stuff might break in corner cases, and I need yet to make the default delegate initializer be a no-op, and I need to make instance binding work so a instance/instance-method pair can be pushed into a delegate, but the 'simple' stuff works:


public class foo{
public delegate operator int op(int lhs, int rhs);
public delegate void DrainA(a instance);

public void doIt(){
print 100 op 100 " \r\n";
}
}

public static void bar(){ print "BAR!\r\n"; }

public goose class a{}
public goose class b:a{}

public static void AorB(a A){ print "A\r\n"; }
public static void AorB(b B){ print "B\r\n"; }


// plus and minus are the built-in names for + and - respectively.

public static void main(){
local foo x = new foo;

x.op = plus;

print x.op(2,2) " \r\n";

x.doIt();

x.op = minus;

print 2 x.op 2 " \r\n";

x.doIt();

//x.op = bar; // error.

x.DrainA = AorB;

local a A = new a;
local a B = new b;

x.DrainA(A);
x.DrainA(B);
}

// 4
// 200
// 0
// 0
// A
// B



Telastyn

Telastyn

 

Paying the Piper.

Not much Tangent work this week. I fixed a bug with this-methods where it tried to make the parameters of the method into member variables (thus breaking scoping rules) and poked a bit more at the importer.

Mostly because my partner in crime was in a musical last week (hence all the free time for mucho Tangent work). Since the musical is over, there's tons of free time for social obligations to make up for the free time. It's not all bad, I get to think of design problems and solutions while driving around the metro for these social excursions.

I ran across two semi-major problems during these thought processes. Firstly, the assignment operator doesn't currently exclude methods. So you can try to assign something to the method itself (rather than the result). It should still do type checks, preventing most stuff. But if you actually assign a method to another it might pass the type-checker. It probably won't do what it's supposed to, and either way standard methods shouldn't be assignable. So I'll need to fix that (probably while implementing delegates).

I also realized that my naive approach to the importer which was valid when I figured that I'd compile to C# is no longer valid in the current state of things. Basically, I don't sufficiently abstract the difference between access and storage for members. So if a type inherits from a .NET type, and the .NET type is run in a .NET method, and a member changes, the Tangent type won't see/use it. So I'll need to change .NET imported fields into Tangent properties that access a common store.


// essentially:

// .NET
public class foo{
public int x;
}

// Tangent
public class bar:foo{
}

// The tangent final class would look like (pseudo-internals):
bar inherits goose foo,
public int x = 0;
private .NET foo = null;

// leading to problems if a .NET method changes the internal foo.

// It needs to be
bar inherits goose foo,
public int x { get { return(__internal.x); } set { __internal.x=value; } }
private .NET foo __internal = null;




But that requires me to implement properties.


And I ponder what next to do as an example app. The number guessing app others are going is too bleh and trivial. I don't care about game-centric-ness so much. I was thinking about doing the C# workshop stuff, but those by and large require collections (which require generics [yes, require. I'm not using that 1.0 tripe]). Bleh. So many impediments. No matter. I'll pick one of these (probably delegates) and get it knocked out as time allows.

Telastyn

Telastyn

 

Tangent: Imports #2

No work today. Upkeep of the social life. I did manage to track down the method -> group inheritance bug since I last posted and did some cleanup of the import. It's actually pretty nice now. I changed int to work off the import rather than the hardcoded type and all of the hard-coded operator things used the new type without complaint. print properly picks up and uses anything with ToString() defined. Overloaded methods are properly imported as a method group (like System.Byte.ToString()).

The current test code, with foo (an empty class; essentially System.Object), System.DateTime, System.Byte, and System.Int32 explicitly imported:


public static void main(){
local foo somefoo = new foo;
print somefoo.ToString();
local Byte testByte = new Byte;
print \" \" testByte;
local DateTime dt = new DateTime;
print \" \" dt;
local int x = new Int32;
print \" \" x+4;
}

// TangentImportFiddling.foo 0 1/1/0001 12:00:00 AM 4




I need to do some review on the results (especially the property looking things), but here's a cursory output from what gets imported currently for System.DateTime (should be 2.0 version):


Private get_InternalTicks
Private get_InternalKind
Public Add
Public AddDays
Public AddHours
Public AddMilliseconds
Public AddMinutes
Public AddMonths
Public AddSeconds
Public AddTicks
Public AddYears
Public virtual CompareTo
Public virtual Equals
Public IsDaylightSavingTime
Public ToBinary
Private ToBinaryRaw
Public get_Date
Private GetDatePart
Public get_Day
Public get_DayOfYear
Public virtual GetHashCode
Public get_Hour
Private IsAmbiguousDaylightSavingTime
Public get_Millisecond
Public get_Minute
Public get_Month
Public get_Second
Public get_Ticks
Public get_TimeOfDay
Public get_Year
Public Subtract
Public ToOADate
Public ToFileTime
Public ToFileTimeUtc
Public ToLocalTime
Public ToLongDateString
Public ToLongTimeString
Public ToShortDateString
Public ToShortTimeString
Public ToString
Public ToUniversalTime
Protected virtual Finalize
Private dateData




I want to hunt down how to do reflection on overloaded operators, work a little on adding namespace/FQN support, and then I'll look into just using type importing rather than having (most of the) built-ins. It'll save me having to re-do the built-ins when a new version arrives, save me having to hand-craft tons of fields/methods, it'll provide practical avenue to get the importing working, and it'll be a good mechanism to test/work-with extending .NET types via Tangent partial classes.

Still in the queue: absolute path declarations, better user errors, polish on this-methods and inheritance and multiple dispatch, and syntax for these imports (if it should be in syntax at all; perhaps just the current using clauses).

Telastyn

Telastyn

 

Tangent: Imports

I spent about 30 minutes revisiting my half-implemented .NET import support, and then about 3 hours tracking down bugs. I've been thinking that there's a problem with how the imported types are assigned. Basically, when the import comes across a built in type, it should just use that. But the reference for 'just that' kinda changes during the compilation process, and the imports suddenly have the same type with a different reference.

But if it were that, I'd have found it by now. I've found other bugs with things during the process... The problem perhaps with 'getting stuff done' sans polish. Rough edges kinda hurt at times.

Anyways, the infrastructure for imports is there, and works for the semi-trivial case where object.ToString() works and is usable by print. Right now, only instance fields and methods work. Everything else is ignored (not exposed to Tangent; it's still there and functional). Anything that isn't a value type or contains a default constructor is ignored. Method overloading works (though there's a bug with type relations between AnythingWithToString and something like Byte.ToString that has 2 implementations), and Tangent should dutifully respect .NET inheritance. All .NET types are goose types (except interfaces; and this is precisely why goose-types exist really).

Everything is currently imported to the global namespace; no path intelligence. And the importer will recursively import depended types, but smart enough to not do so forever. I'll need to add support for referencing more assemblies than the default too eventually. And a lot of the corner cases probably aren't ignored gracefully at the moment... myeh. Now for some recreation, then I'll probably look into these bugs again later tonight.

Telastyn

Telastyn

 

Tangent: This methods

~1 hour; implemented 'this methods':


public class speaker{
public string text;
public void this(){
print text " \r\n";
}
}

public static void main(){
local speaker cow = new speaker;
local speaker sheep = new speaker;

cow.text = "moo.";
sheep.text = "bleat.";

cow();
sheep();
}

// moo.
// bleat.




And again there's some polish to be had. I'm not sure how well these puppies work when inherited (should be allowed, probably works for the straightforward case) or in various combinations when they're stuffed into groups. There's also the possibility of some wonky behavior with dispatching if these things are in a group (since the 'this' parameter is the functor itself).

I hope to get the absolute path declarations or some basic .NET importing done later today.

Telastyn

Telastyn

 

Tangent: Virtual Methods

Quick work on virtual methods and type comparison between unbuilt and built types.
This now works:


public goose class A{
public virtual void moo(){
print "moo.\r\n";
}
}

public goose class B:A{
public virtual void moo(){
print "MOO!\r\n";
}
}

public static void main(){
local A foo = new B;
local A bar = new A;
foo.moo();
bar.moo();
}





I'm sure there's some details which need polished. Right now for example I'm pretty sure all methods are treated as virtual, so that will need to be fixed. It probably won't work exactly like traditional virtual methods, but should work more like them. There's probably also bugs with multiple inheritance and virtual methods and/or virtual dispatch where there's a 'gap' in the override sequence. Little things, mostly polish/bugfixes.

This though does not currently work:


public goose class A{
public virtual void moo(A rhs){
print "AA\r\n";
}

public virtual void moo(B rhs){
print "AB\r\n";
}
}

public goose class B:A{
public virtual void moo(B rhs){
print "BB\r\n";
}

public virtual void moo(A rhs){
print "BA\r\n";
}
}

public static void main(){
local A a;
local B b;

a.moo(a);
a.moo(b);
b.moo(a);
b.moo(b);
}





b.moo(a) returns an ambiguous syntax error because there's some error while checking if A. Going to play some games, watch some football, eat some red vines and relax. I'll fix that up later tonight.


Also, while I'm thinking of it; I changed the default initializer for classes to be new rather than null. It will be more common I think and make people write out = null, which should serve as a reminder to deal with it before use. That'll be something for playtesting I think.


[edit: 3:30 hr later (30 mins of debugging)]
Just a few little changes and the multiple dispatch in source works. Again, there's probably some polish to be had; keeping types disparate, adding warnings for ambiguity, a means to select 1 method from a group manually, IDE support for seeing what is in a group, and how it interacts with X.

Sadly enough, that covers the things I was aiming to complete over the weekend. So tomorrow I'll work on targets of opportunity.

Probably either absolute path declarations or this methods

absolute path declarations are essentially operator overloads. It's adding an arbitrary member to mix into a type from a different scope. The @ symbol will be used to prefix the identifier to do that:


public class vector{
public operator vector @plus(vector lhs, vector rhs){ ... }
}



Will mix-in this method definition with the global member plus (which happens to be the name of the + operator). Though it'll have access to vector's scope, and thus private members just like operator overloads seen elsewhere. It might also have some use for friend functions and similar work-arounds. I'll need to think more about the details in implementation, and the scope of this.

this methods will be a different syntax for C++'s operator(). For those not familiar with C++, it allows a class to act as a creatable method. The special declaration has access then to stored variables within the instance.


public class PlusX{
public int X = 5;
public int this(int rhs){
return(X+rhs);
}
}

public static void main(){
local PlusX plus5 = new PlusX;
local PlusX plus1000 = new PlusX;

plus1000.X = 1000;

print plus5 50 " " plus1000 50 " \r\n";
}

// 55 1050



A contrived example, but a more straightforward way to create some state to accompany event handlers (and similar problems). Code in C# tends too often to either tie one method of a big class into the event or require some (unintuitive) implicit conversions to make a small class act as a functor. Since Tangent will allow (and advocate) more emphasis on methods, quickie functor creation becomes more important.


Telastyn

Telastyn

 

I am going to hell.

As if the inordinate amount of evidence wasn't already enough:


public goose class A:C{
public int a;
}

public goose class B:A{
public int b;
}

public goose class C:B{
public int c;
}

public static void main(){
local A foo = new A;

print foo.a foo.b foo.c;
}




Coworker today asked how the mix-in implementation handles cycles. This will compile nicely and print 000. So umm, yeah. It handles cycles, and is by most moral standards one of the more evil things I've done in recent memory.


In a mildly less evil Tangent example, I worked quickly this evening on name collisions involving methods. Just the simple case where the methods are unrelated and should thus merely overload.


public class bar{
public int x = 4;
public int plusX(int y){ return(y+x); }
}

public class foo:bar{
public string plusX(string y){ return(y+x.ToString()); }
}

public static void main(){
local foo instance = new foo;

print instance.plusX(2) " " instance.plusX("cow");
}




And prints 6 cow4. A little more work than I expected due to some of the binding rules regarding member access. Works now though.

Next will probably be proper virtual methods.


[edit~! (5 hours and many TF2 kills later)]

I added in support for comparing the half-built types so that one or another may be preferred during a name collision. It's mostly not there still though. There's nothing to compare half-built types and built in types (or included from a library once that exists). I also currently punt if the types being compared have any name collisions themselves.

So basically, it currently only works for the ideal conditions. Baby steps.

Example code:


public goose class A{ public virtual string whoami = "A"; }
public goose class B:A{ public virtual string whoami = "B"; }

public class foo{
public virtual A x = new A;
}

public class bar:foo{
public B x = new B;
}

public static void main(){
print (new bar).x.whoami;
}



Which will nicely print you B. If a name collision has compatible types (and the initializers + abstract/virtual work out sanely), then the most specific type is used.

Telastyn

Telastyn

 

Tangent: Mix-ins Part 1

Fixed the bug in the op-constraints. I must've written them while sleepy, because I made the op-constraint object a mutable class. Things that get bound with types need to be immutable (and you get a new type back). This led to problems when I ran two in a row (in this case, assignment). I would bind the two types to the op constraint, and it'd check them as passed. I would bind the next two types, but there was no type-parameters left to bind to, they were already filled! So when it went to check, it saw the old 2 params, and passed that along too.

Oops. They're now nicely immutable little things that return new things when bound with types.


I decided in the past few days that the name collision and inheritance cleanup needs three parts. Part one will be converting the mix-in operator from working on well-defined types to the half-defined types during compilation. Part two will be converting type comparison to the half-defined types available at compilation. Part three will be auto-generating new types for inherited member-methods.

That will allow (respectively):

- name collisions to be resolvable for cases where the types are identical. Abstract and virtual and initializer resolution should work.

- name collisions to be resolvable (or less punt-able at least) where the types are not identical. There's likely to be some cases where it's indeterminable. This is going to be the hardest part.

- methods -> method groups, virtual dispatch, snazzy stuff.


I got part one done tonight, which also included a mechanism for block comparison and some cleanup on the built-in types. Example!:


public class bar{
public int x = 4;
}

public class foo:bar{
public virtual int x = 0;
public int x = 4;
public abstract int x;

}

public class a{
public virtual string moo = "a";
}

public class b:a{
public virtual string moo = "b";
}

public static void main(){
print (new foo).x " \r\n";
print (new b).moo;

}



(the multiple declarations per class is odd, but allowed for now. It simulates inheritance, which mechanically adds members from the inheritees to the bottom of the declaration [and are then consolidated to 1 per name])

This code works fine now, printing:

4
b


bar::x is consolidated into foo::x since they have the same initializer. foo::x = 4 beats out its brethren because it's not virtual (or abstract). virtual on fields implies that the initializer may be overriden. The second half of the program shows the picking if both parts of the name collision are virtual; the type doing the inheriting (or to the left of the mix operation) wins.

Telastyn

Telastyn

 

Working Weekend

My social life is on hiatus until Sunday evening since my partner in crime is busy doing a play. Bad for me, good for Tangent. The plan for this weekend is to get inheritance working fully. Properly resolved name collisions, fix the bug in op constraints, and get a few nice test cases for the various combinations of collision resolution. Oh, and a release for people to inevitably break it.

I kinda only expect to get 1/3 of that actually done, but whatever. That should leave me the rest of the month for the other 2/3, bug-fixes from it, and one or two other features. Probably absolute path notation or signature type expressions or non-trivial type expressions or perhaps half-assed .NET import support. We'll see what the demand is, and where whimsy goes. Generics in source is the next big thing in the queue. I'd kinda like to get a few little polish-y things done first.

Which is maybe funny since the hard part of generics is already there for types (though might need some tweaking), I just need to implement the syntax and compilation bits into it.

Telastyn

Telastyn

 

Tangent: Inheritance Bugs

I tracked down a few little bugs with Tangent inheritance. One was just something dumb that I forgot to do that prevented classes from having their inheritance lists dealt with. The second was a parser design error that saw foo:bar,baz{} as foo inherits a tuple of type bar,baz. Fixed that. Now parens will be required to inherit from a tuple type.

There's currently a bug with initializers during inheritance when there's a member collision, and I need to review some of the other stuff that handles member collisions. Otherwise inheritance of simple types, even multiple inheritance works now. Don't collide member names and it should be fine.

I might get to the other stuff, might not. I'll either work on that or get a release out Wednesday when I've time again.

Telastyn

Telastyn

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!