Advertisement Jump to content
  • Advertisement
  • entries
    359
  • comments
    237
  • views
    189598

About this blog

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

Entries in this blog

 

Tangent: Super-alpha

After looking and asking about, I made an assembla page for Tangent. Included is a release for version 0.1 and some wiki/documentation stuff. It should only require .NET 2.0, and ideally just extract from zip and run the exe.

Telastyn

Telastyn

 

Optimization Tidbit

Quickie profiling showed that the majority of Tangent's slowness for the test app was indeed around 1 method (36 seconds of 50 on the slow machine; the method to select the correct method from a group based on runtime type). I added caching to it (since given inputs, the output will be identical) and dropped that from 36 seconds to 0.6 seconds. Tangent is now 20-30 times slower than C# which is acceptable (imo) since it's currently running in a naive-VM with nil optimization during compilation.

[edit: and tonight I re-did escape sequences so that they work. Woo. Over the weekend I'll probably look into writing up some documentation, and investigating a home for the code/docs/faqs/forums/crap so I can let people gouge themselves in tender places with the language. First run at the very least will not be open source; sorry.]

Telastyn

Telastyn

 

Pie.

Spent the evening doing yardwork. Before I even got in the door, some jackass (a realtor I suspect) pulled over and chastised me for not having any 'pride of ownership'. I was going to mow anyway, and seriously... only the weeds were more than 4" high. There's a reason I chose a house on a dead end street in a climate that kills/covers the grass for 6-8 months out of the year. And it's not because I want to show off my fucking gardening skills.


My coworker whom I bounce ideas off of (and is also working on a toy language) challenged me to make something semi-non-trivial to prove that tangent actually works. He chose 'estimate pi to X digits'. We ended up settling on the simple wikipedia algorithm ((4/1)-(4/3)+(4/5)-(4/7)+(4/9)- ... )

After a little debugging (~10 mins) Tangent's version was up and running:

public static void main(){
print pi();
}

public static decimal pi(){
local decimal rtn;
local int denominator = 1;
local bool isPlus = true;

do{
if(isPlus){
rtn = rtn + (4/denominator);
}else{
rtn = rtn - (4/denominator);
}
isPlus = not isPlus;
denominator = denominator + 2;

}while( denominator 10000 );

return(rtn);
}



Bad news is that it took ~20-40 seconds to run. Curious, I knocked up the same thing at home, and a C# equivalent. Tangent: 6 seconds; C#: .006 seconds;

Performance is the last thing on my priority list... but jeez. It'll be good to keep the 'compile down to C#' or even CIL option in mind as I do stuff.


Telastyn

Telastyn

 

Tangent: Backfill.

I went back today and filled in most of the missing major operations that Tangent was missing. At the moment, it's mildly stable as far as half-assed toy languages go. I'm considering polishing off a new nagging issues (escape sequences come to mind) and making a release for people to fiddle around with it.

The benefits will be that I'll get experience doing a release, I'll be forced to provide semi-coherent documentation, forced to get some source control/bug tracking implemented and I get the pressure to not look like a moron. And of course there's the feedback/suggestions/questions, though I suspect limited interest and limited intelligent feedback. I'd expect questions ('how's this work?','why's that not done?','what kind of idiot made that decision?') to be the most helpful bit there. See what people use/want.

The downsides are that nobody'll use it, the few bug reports I get will be of known bugs or dumb things I missed or 'works as designed' bugs, and that it'll be a bit of work for stuff that isn't making more stuff work. Plus it can't yet work with .NET types, so nobody will be able to do anything interesting.


I'll think about it over the weekend, and prolly aim to clean up the escape sequences since that needs to get done anyways. By the way, here's the current status of what is there and works:

Built in Types:
char
string
int
decimal
bool
true
false
Any
null
void

Operators:
==/!= for booleans and char,string,int,decimal
+ for char,string,int,decimal like C#
-*/negate for int, decimal (unary - doesn't work)
= for arbitrary types
, for arbitrary types
||/&&/^ for boolean types
new for arbitrary types
print for anything that implements ToString()

Control Structures:
if
while
do/while
method group dispatch
[edit:]
else
return


Type Definitions:
Classes
Member fields
Initializers
Member methods
Static modifier (mostly untested)
Abstract modifier
Goose class modifier
User defined operators

Language Features:
Static duck typing
Inferred order of operations
Methods/Method Groups as objects


Myeh. Doesn't look like much in an itemized list. Boo itemized lists.

Telastyn

Telastyn

 

Off day

Had the day off today. For all the terrible benefits my work provides, they do give us tons of PTO days (though they prevent us from carrying over more than a week. Bleh!). I'm not a big fan of going places or lengthy vacations in general. So I enjoy taking a nice constitutional day every 3-4 weeks.

Today was one of two sort of constitutional days. The first generally involves playing some new game for 16 hours and then passing out. Good times. Today was the other though. Slept in, worked on Tangent a little, then went out. Played some pinball, watched the Dark Knight, had a glass of wine and a big juicy steak. Good times.

The local theatre has a Pirates of the Carribean machine that's in... poor shape but playable. I have 2 of the 5 scores there now. The machine isn't fantastic, but it does draw from Lawlor style designs. Mostly though it has one noteworthy feature. Most games, when you get a multiball you can't advance the game state. You kinda just get the multiball, rack up points, and then the state reverts to what it was before. The PotC machine doesn't. You can trigger stuff, including more multiball goodness and jackpot setups while in multiball.

Which is fantastic for me, since my strength at the games is tracking the multiple balls in parallel. Pinball games generally follow the same pattern for me. I show up to the movies 60-90 minutes early and toss $2 into the machine (5 credits on most). I'll play about 8 games, set 1 high score, leave 2 credits on the machine and have to run to not miss the previews. Today was no exception.

As for the Dark Knight: It's good. Best movie of all time? Not by a long shot as far as I'm concerned. To me it's a lot like the Lord of the Rings movies. A little overly long, some solid casting, very memorable performances, but just missing that... something that bumps a movie from a really good film to an all-time film. Y'know? Just that something novel or exceptional that sets it apart from the rest.


Tangent work focused on 'minus'. I have it now that for L-R if L+R exists and -R exists then L-R == L+(-R). The type verification stuff works at the moment, but there's a bug/incomplete implementation that is preventing it from actually generating the code for that. I'll look at it later.

Oh, and saw Hellboy II earlier in the weekend. Entertaining and shiny. It unfortunately suffered from some dumb plot elements. One of those movies that would've been 5 minutes long if characters were even mildly intelligent and solved the plot's conflict there and then. Appleseed Ex Machina was like that. Well, it was way worse than that. I watched that steaming pile of crap at the recommendation of coworkers. Ugh!

As for wine and steak, the Argentinian Malbec tonight was pretty good. The steak was... a good cut of meat. The skill of the chef was kinda plain though; it was just steak. 'Bella' up here in Blaine,MN has the best steaks I've ever had (and good vegetarian dishes I hear from those who eat such things). The key there though is the sauce that is over and partially cooked onto the steak. A lot more skill from the chef required. If you're out in the San Jose area check out Henry's Hi Life. Hard to find, looks like a dump outside, looks worse inside, but the steaks are top notch and you get all you can eat garlic bread.

Good times.

Telastyn

Telastyn

 

Tangent: Assignment and Generic Type Constraints

I spent about 30 minutes this morning implementing generic type constraints into the backend and making the assignment operator work with arbitrary types. Pretty damned skippy for something I expected to take me closer to 6 hours to get working even for the basic stuff.

Morning tests:
// works
int x = 42;

// compile time error.
int x = "foo.";

And the type signature for that would look something like this in syntax:

public static void assign
where RhsType>=LhsType
(LhsType Target, RhsType Value);



Though the nitty gritty of the assignment needs to be done under the hood. The type verification and order of operation inference uses that though. Multivariable operator requirements will probably use that sort of syntax, though single variable stuff will use something more like:


I'm moving the where constraint bits from C#'s position so that I can support a future enhancement. I think that this scenario will arise a lot in Tangent:
move Ogre to Castle

To implement that properly, you'd have to make a move method that took a parameter and returned an intermediary object that takes a 'to' operator (or perhaps what 'to Castle' returns). And I think that such implementations will kinda suck. So something akin to this would be ideal:
public void move(Unit Actor) to(Location Target){
// move actor towards target
}

with the compiler itself doing the generation of the intermediary anonymous object. Having the where clause follow the parameter stuff would cause ambiguity. I am going to avoid that enhancement for 1.0 though I think. This sort of thing strikes me as something that I will gain a lot of insight about its requirements and usage patterns as I gain experience with writing Tangent code. Imagining a problem is one thing; experiencing the problem is another.

Next in the queue is probably some operator requirements so I can nicely implement a default lhs exists and lhs==rhs exists, default to (lhs if no more specific overload exists) and similar constructs. Then more fleshed out operators for the built-ins.

Telastyn

Telastyn

 

Tangent: Tuples

In code tupling now works. I don't like tuples. (int,int) is inherently worse than (int x, int y) which is in turn worse than struct MousePositionCoordinates{ int x, int y }. But let's face it, having anonymous fixed length heterogeneous lists is damned useful for particular bits of code like parameters.

So this code for example wouldn't work until tupling was working:




As a side effect or prerequisite, this means that generic method invocation (including type inference) is working under the hood. The comma operator is a little special, but as far as the type system is concerned it takes 2 parameters and returns a tuple that is the types of the two parameters. The operator sugar handles getting two objects into the parameter tuple in the first place.

Next in the queue I think will be generic constraints so that I can implement the assignment operator for arbitrary types.

Telastyn

Telastyn

 

Tangent: Generic Methods part 1

While Team Fortress 2 has regained some of my attention, I have not been completely idle with Tangent. I've been working on making tuples work like I want them to, which indirectly requires that generic method invocation work. I've been doing a little research into methods whose declarations reference the same parameter twice.

One parameter has worked under the hood for some time now. Multiple parameters weren't tested, but should work and multiply referenced parameters should've worked. They didn't quite work. A little work later, and now they function as they're supposed to. A bit more work later and the AST generation could detect and handle generic methods.

Unfortunately, tupling still doesn't work. Despite identifying the problem, designing against it, coding against it, I've still run into this:

What does a,b,c,d represent? Intuitively it should represent a heterogeneous list with 4 elements; a flat tuple. Unfortunately, it represents (((a,b),c),d) at the moment, despite designing specifically for that special case. And I could go for something like that (it's essentially every functional language's list backwards), I don't want to. Further, a analogous set of bugs will arise for other operations like that.So that means a modest bunch of changes will need to be made to support 'lazy' expressions for method declarations.

On a more positive note, the type inference works now. 1,2,3 resolves as (int,int),int with the generic-ized comma operator. And for those curious about the link above, Tangent's implementation for

Method(T lhs, T rhs);
Method(b,c); // where b & c inherit from a

will properly detect and use the most derived common base class for the parameter T. To make a method require the same exact types, something like this will be required:

Method(T lhs, V rhs) where T==V

Mostly because I couldn't think of a non-terrible way to declare 'give me the most derived base class' if just using T in both places signified 'exactly equal'.

Telastyn

Telastyn

 

Tangent: Parsing

If I were smart, I would. Unfortunately, I am an idiot.

I like spirit. It's one of the few parser libs that hasn't quickly and surely bitten me in the ass. Antlr bit me in the ass. A few others I don't remember bit me in the ass. Hence bitten, I'm a little hesitant to try again.

But I hate C++ (these days). And I really hate C++ when I need to do string manipulation and work with .NET type imports. The code for tangent is all C#. The parser, the compiler, the type system, the little interpreter thing, the crappy IDE... and currently clocks in around (only) 13000 lines for everything.

The parser itself is a series of classes that are compositioned together much like spirit's setup. Operators are overloaded to allow that to be natural and pretty easy to do. A quick example:


IfStatement = new DebugTokenParser(new LabelledTokenParser("IfStatement",IfStatementParser));

ElseStatement = new DebugTokenParser(new LabelledTokenParser("ElseStatement", ElseKeyword + (Block | IfStatement)));

WhileStatement = new DebugTokenParser(new LabelledTokenParser("WhileStatement", WhileKeyword + new LabelledTokenParser("WhilePredicate", Expression) + Block));
DoWhileStatement = new DebugTokenParser(new LabelledTokenParser("DoWhileStatement", new LiteralTokenParser("do") + Block + WhileKeyword + Expression + SemiColon));



A little wordy, though the debug/label stuff is ancillary to the functioning. All parsers (lexer objects actually) take a string and a begin/end indexes, and return a object indicating success and the length of the match (similar to Regex's Match). A similar setup exists for token parsers which deal with the resultant tokens/results.


But realistically, the parser isn't very complex for Tangent. In most languages, after parsing you know pretty much exactly what is going on. foo.method(a,b) + 4; will parse out as a member access, an invocation with 2 params and add the result to a literal. x= 2+3*4 will parse out with the correct order of operations based on expression-element ordering.

Tangent does not. It cannot. A good example is in the post from July 6. What would a normal parser do with speak new cow? It'd probably pick out the new cow, but basically it's an identifier stream with no context. Tangent's parser (for actual blocks of execution code) essentially comes down to an Identifier stream.

Most of the useful stuff is moved to a later step kinda half way between traditional parsing and syntactical analysis. At that point, types are well defined. The identifier streams are processed to pick out an interpretation that makes sense. To make a traditional syntax tree from a stream of tokens, but unlike other languages the type information is available.

The type information is used then to enable user defined operators. It's also used to cut down on LISP syndrome (parenthesis everywhere). All statements must result in void. With this stipulation, and the type information handy a good number of non-sense interpretations are thrown out. The parens are then only required for standard issues where you need to change the default order of operations.

aside: for tangent, that boils down to:

- if 1 token in stream, and token == target type, return success.
- foreach token
-- if binary op and takes (token-1,token+1) replace the 3 tokens with 1 that has the effective type as the return type of the op. Recurse. If recursion returns failure, continue.
-- if unary op and takes (token+1) replace, recurse, continue on fail.
- fail if we finish the loop.

so that means 2+3*5 will not do what you expect. A small price to pay imo.

And note that all methods are unary operators. Some just take tuples as their one parameter. Binary ops are sugar for that 2-tuple.

It's late now, and I cannot think of more interesting tidbits that I am leaving out. Ta-ta for now.

Telastyn

Telastyn

 

Tangent Parameters, Third

I implemented a mildly hackish implementation for parameter names. This now works:

public class cow{
public int x = 4;
public operator void moo(int a, int b){
print a+x " " b;
}
}

public static void main(){
90 (new cow).moo 42;
}



I also picked up Portishead's Third. It is disappointing. I love the band's sound, and like the haunted blues sort of stuff they make with it, but the new CD is just way out there artsy. Every time something vaguely approaching a chorus or catchy bit gets developed, it's overplayed by some offbeat dumb machine or ukulele or what sounds to be half of a clarinet... Kinda sucky.

I kinda wish that more bands tried to copy them. Nothing I know of even comes close more than a few passing measures. Disappointing.

Telastyn

Telastyn

 

Tangent: Member Initializers

Thanks to all the recent posters showing random encouragement and appreciation. A welcome change, though mildly surprising. Regardless, such encouragement is certainly... encouraging. Keep it coming. Questions and specific critiques of the language are also welcome. If I'm wrong or dumb, it's best to at least know about it; especially now while I can still adjust the design fairly easily.

After getting the world's worst IDE thrown together I turned to member variables, their initializers and correctly detecting (and using) 'this'. The work was fairly straightforward since it'd all been planned for. Just a matter of implementation, test, bug-fix, repeat.

The final (ugly) test code [compile&run output at bottom]:



It's mostly ugly because assignment (besides the core types) doesn't work yet, and referencing parameters by name doesn't work yet. Hence the convoluted instantiation->call code, and the Parameters access rather than just using the name. Though realistically, I think member operators like this won't be public. They'll be assigned at runtime via delegate and used within the class and its kin, or the class itself will be the operator (similar to C++'s overloaded operator()).

Parameter referencing by name is probably next. Assignment and tuple operations are important, but will require generic method invocation to do them right.

Telastyn

Telastyn

 

Tangent: IDE?

I had a little time today so I fixed up member access. foo(a,b,c).bar() now will work as expected and a few other oddities around member access have been corrected. As an added bonus, the code around them is smaller, cleaner and more kickass.

I also took a few hours to knock up the worst IDE known to man. Textbox for input, textbox for output, button to compile. Woo. I'll likely need to add a little to it because I suspect Tangent to really benefit from IDE support. Stuff like knowing what that identifier actually is, what that statement really will resolve to, what the order of operations for that is going to be. Usually I expect that to be clear, but it'll be easier to write ugly/obfuscated code and I think even good coders will run into scenarios where the IDE will help.

Now I have somewhere to add that, and a practical bit of code to better judge what is required/useful.

Telastyn

Telastyn

 

Tangent: method dispatch

Not much work today. It seems as though 2 full days of productivity is really my limit. I did though just get an hour in, and got my target code working. See, each session I devise some test code and then make it work. Helps keep me focused on getting stuff done. Anyways, I suspect that I might have gotten this one working... poorly. This specific example works, but I think marginally different examples might not work. Ah well; problems for another day.

This example might be interesting though, as it's another little tidbit that is a language feature rather than a reimplementation. Today I made the code to create method groups from declarations to work, as well as the code to pick the most specific version. This was kinda there before but not working or even half-assedly tested. Anyways, on to the show:


public goose class Cow{ }

public static void speak( Any Beastie ){ print "Enh?"; }
public static void speak( Cow Bessy ){ print "moo."; }

public static void main(){
speak new Cow;
speak true;
}



Which has the mildly intuitive result: moo.Enh?

This is one of the key features that the design has settled on. The initial impetus for Tangent was the innumerable 'game entities as components' threads. One of the key problems people run into during these threads is how to handle capabilities for their entities. Item.Drink makes sense when the item happens to be a potion, not so much when it's a sword.

Traditional inheritance makes that kinda awkward to implement, and very messy once you get into multiple behaviors for one action or worse yet when you get into binary operators (ant lift boulder vs ogre lift boulder). And even mix-in inheritance kinda runs into problems with ordering and specialization.

Free functions are better, and you can do the specialization for them, but they too will need to be combined so that disparate bits of code can specialize them as needed. I've not yet implemented it, but this sort of thing will often work using this alternative syntax:


// optional file
public goose class Cow{
public static void @speak(Cow Bessy){ print "moo."; }
}

// standard definition
public static void speak(Any Beastie){ print "Enh?"; }



Where the @ allows for a explicit path for the definition. Importing the second file/library would then add the overload to the operator. Might lead to more conflicts than usual. Will probably lead to easier solutions since there's less adapters/glue needed when the method itself acts as the interface. Dunno. I need to get things working more to use it more to see how some of these things work in non-trivial code.


Oh, and in case anyone was wondering about the odd looking class modifier 'goose'... it's the current name for an identifier which disables the duck typing for that class. Other classes need to explicitly inherit from it to be considered a sub-type. It's also useful for cases like these to differentiate classes that would otherwise be identical.

Telastyn

Telastyn

 

Tangent: Shortcuts

Alas, not the good shortcuts. A project of this magnitude takes a lot of work, regardless. No, no... Shortcut-ing operators! While most operators can be done nicely in code with some simple predefined stuff or via redirection to C# stuff, &&, || and other shortcutting operators cannot be since they divert the usual processing steps in the little test interpreter thing I have.

Taking a little care to make this work in a way that allows bool && bool to be used/combined like other methods while still behaving specially, I implemented the simple bool versions for && and ||.

An example!

public static void main(){
if(true or null){
print "moo.";
} // else, null ref exception!
}



Some notes:

This compiles fine because (at the moment) null is a sub-type of all types (except void and itself). So passing it in place of a bool is kosher.

false or null and true and null will throw exceptions because I don't think it's completely sane to treat null as both true and false, and returning null kinda sucks too. 'tis perhaps one of the first things to look at if I implement some sort of value types or value storage.

Telastyn

Telastyn

 

Tangent: on parens

After this morning's work, I was interested in getting class instantiation working a little more solidly. One thing I realized should be possible even without assignment is a statement like this:

(new cow).moo();


Little problem though. Thinking through the original design, I focused on two paren uses really. Standard order of operation preferences [ 5*(2+3) ] and grouping invocation arguments [ foo(a,b,c) ] which basically amounts to the same thing.

Now, the problem comes that all Tangent expressions are passed a type as they're processed; the type that we're looking for that expression to be. For 5*(2+3) the parens are passed the various acceptable types for the right hand side of *(int,?). A full statement in Tangent must be of type void. It's mainly used to help cull ambiguity, but can later be used for method picking based on return type.

So the problem is that paren expressions are black boxes. You pass in a type and get back a list of permutations that would result in that type. So in the above example, the compiler is oblivious as to the type of the parens and fails. Kinda sucky.

I fixed this up this (and some of the problems surrounding instance methods) so the above code works. Basically a little extra work to see if the paren can have one and only one possible type.

I might need to undo it though. The change will cause this sort of code to fail:

foo(a,b,c).bar();


and require this:

(foo(a,b,c)).bar();


Which is a little icky, and a little at odds with the behavior of everything that is not member access. Might be one of those things I leave to see what sort of beta feedback I get.

Telastyn

Telastyn

 

Tangent: Morning Update, now with new!

'new' now works in Tangent. Sorta. Constructor arguments aren't yet supported, which is mostly fine because Tangent classes can't have traditional constructors. But having new is kinda useless because op-assign isn't templated, so only works with built-ins at the moment. Further, member function definitions are kinda broken. So basically all the grammar and compilation for new is there, but you can't actually use it for anything...

Telastyn

Telastyn

 

Tangent: Inequality, multi-file building

The afternoon alas seems to have been sucked into Settlers 2 (which then crashed) and stupid little bugs. But after getting through all that, I have a treat for you my readers! (ie, rip-off...) Some testing code that might be mildly interesting since it's not just reimplementing C#.

The test is simply implementing inequality since I did equality this morning. Since I wanted to implement it directly in Tangent rather than the icky hardcoded AST that is required if I didn't, I wanted to add the ability to simply include that source in with the build. That in turn required a little work to make the compiler handle multiple sources in the build. I'd kinda done that when making the compiler bits, but lost track of it a little in the drive to Get Stuff Done.

Anyways, that works now. The test code in question:


// file0

public static void main(){
if(true inequal false){
print "moo!";
}
}


// file1

public static operator bool inequal(Any lhs, Any rhs){
if(Parameters.tuple[0] == Parameters.tuple[1]){
return(false);
}else{
return(true);
}
}








And there you are. User defined operators in source. Even the symbolic operators like '==' map to a word-operator. '!=' maps to inequal and could be used in main and will have the same meaning. This in turn is a simple definition that makes '!=' be the opposite of whatever '==' is defined as (unless a later overload redefines '!=' for the types).

And as you can see, parameter identifiers still don't work. Bleh. 'new' still doesn't work either.

I think though the next thing might be op requirements. Technically, inequal should be defined as a template method that takes 2 params that can be used as lhs == rhs. == takes two Any's so it's kinda moot. That though will be required for the default 'lhs exist.

Enh... maybe not. Should probably stick to the milestones.

[edit: and for giggles, I threw together not and redefined inequality as that. Here you are in the alternative syntax. Enjoy!]


public static operator bool inequal(Any lhs, Any rhs){
return not Parameters.tuple[0] equals Parameters.tuple[1];
}







Parens are only technically required for invocation when the default order of operations isn't sufficient or which method in a group to use is ambiguous. Here, Tangent can infer from the type signatures what order is required. If lhs and rhs were bools here, the order of operations would be (perhaps unintuitively): return((not(lhs))==rhs);

I think that usually this can yield a little more natural code to write and read. It will though I think be very very useful to get some IDE support to display the paren-heavy version for those (suspected infrequent) cases where things get muddled.

[edit2:]
Hrm. An overload of not to take a boolean binary op on the rhs and return the opposite might be nice.

ie:

public static operator bool(Any,Any) not(bool(Any,Any) op){
return (Any lhs,Any rhs)=>{not (lhs op rhs);};
}

// would allow
lhs not equals rhs;




Enh. Would probably have to make the method type a generic and make some way to pull the param types into the returned delegate... Woot, use case.

Telastyn

Telastyn

 

Tangent: While, equals

The entries will come fast and furious over this weekend. Partially because I like getting to a point where I can declare progress, partially because I like writing (despite the appearance that nobody reads my tripe), and because I need to do something while my background game of settlers 2 finishes a catapult.

This morning's target was these four sources:


public static void main(){
foo(2);
}

public static void foo(int x){
print Parameters;
if(Parameters == 2048){
return();
}else{
foo(Parameters+Parameters);
}
}

// and

public static void main(){
while(true){
print "moo!";
}
}

// and

public static void main(){
while(true){
print "moo!";
return();
}
print "bleat!";
}

// and

public static void main(){
do{
print "moo!";
} while(false);
}



And all work nicely (2-2048, infinite moo, 1 moo no bleat, 1 moo) after about 2 hours work. Still in the queue: More operators, 'new' and class declaration polish.

Telastyn

Telastyn

 

Tangent: If and Blocks.

I did a little cleanup on the syntax after yesterday's travails. An 'if' now behaves more like it's supposed to. No semi-colon necessary after one. And blocks have been moved out of expression areas and into more specific use. This might limit a little what might've been capable along the lines of defining new syntax in code to take a block and manipulate it. That though is increasingly seeming a pipe-dream, hence the change.

Work tonight was implementing 'if' stuff, and making standalone blocks work (including deep returns, proper scoping, and so on). The test code for the evening:


public static void main(){
if(false){
print "moo!";
}else{
print "bleat!";
}
}




And I have no boolean operators at the moment to do anything more fancy. The opposite does the correct branch as well. Woo. For tomorrow: while/do-while, more operators, and maybe 'new'.

Telastyn

Telastyn

 

Tangent Tidbits

Minor work tonight on Tangent. I wanted to look into control structures tonight, but remembered I still had to fix the way stuff is returned in the little test interpreter. As it stood, returning from a nested block didn't work right (it wouldn't exit the outer block). So fixing that so that return retained the type info for what it needed to return while also nicely exiting the blocks in turn was a little tricky.


The other problem came when adding in the syntax for a simple if. Here was the syntax definition for that:

ExpressionElement :=
LocalVariableDeclaration (shortcut-or)
IfStatement (shortcut-or)
Identifier | // standard identifier syntax (currently includes keywords)
AnyLiteral | // "string",'char',123,3.14, true/false/null/void
MemberAccess | // .
TupleAccess | // .tuple[index]
TupleOperator | // ,
TypeParameterization | //
ParenExpression | // (Expression)
ShorthandOperator | // +-*/ etc.
Block // {Statement*}

Expression := ExpressionElement*
Statement := Expression ;

IfStatement := 'if' Expression Block ElseStatement?
ElseStatement := 'else' (Block|IfStatement)


And the test source was:

if(Parameters equals 2048){
return;
}else{
foo(Parameters+Parameters);
};





After I tracked down that nasty trailing semicolon after the else block, it still didn't quite work right. It would parse through the if, the predicate, the block... but the 'else' keyword parser was never entered. So, where is the error?






Since an expression is any number of elements, and both a paren expression and a block are elements, it grouped both the '(Parameters equals 2048)' and all of the elements following it into the predicate part of the if. It then hit the semi-colon after the final else and started looking for the block part of the if and failed.

Telastyn

Telastyn

 

Tangent: Cleaning up my mess.

Everyone is going out of town this weekend except for me, so I'll have the long weekend to drink mountain dew, eat swedish fish and work on Tangent. w00t.

As a lead up to that, I had tonight free so it went to do some cleanup work on the method declaration bits. There were... probably 4 major problems with method invocation and declaration as they were naively implemented before.

1. I had to change my approach, which caused me to lose the names of parameters. This kinda meant that you couldn't refer to them within the method...

2. I didn't nicely instantiate the per-instance set of variables for a method. This meant that recursion (and multithreading, if it existed) didn't always work as it was supposed to, since the parameters/return values could be overwritten. Oops.

3. The code to replace type expressions with actual types during compilation was dumb. Super dumb even, so anything more complex than 'void' would run into issues.

4. Unfortunately, the code that defines a Type and the code that defines a Type while it's being compiled are not the same. So any compiled type (say... int, or string) wasn't quite available to the compiling code. And since it's not implausible that a Type is going to be extended during compilation (operator overloading for example) I can't just leave them as plain types.


Mostly though, it was a mess because I've been working on this beastie for a few months now, and at times threw stuff together to make a milestone work. Good for me, since I've a habit of not 'getting stuff done'. Bad for me because it makes messy code. Not super messy, and fixing it isn't too painful these days, but it still requires some time for maintenance.

Problem one is unfortunately not fixed. For now the parameters are available using simply 'Parameters' or 'Parameters[index]'. Problem two was fixed by doing proper instantiation of an execution context per method. Problem three was fixed by adding code for expression understanding and anonymous type creation for each step of the expression. Problem four's solution involved a little special casing and a translation method to convert Types into their unbuilt representations for use and/or extension.

Class declarations sort of work in code now too. You can't instantiate them, and I think there's some funkiness around the initialization bits for them and maybe some of the instance methods. I need to come up with a good test case to work towards.

And for interest, the test Tangent code that I focused on getting working tonight:


public static void main(){
foo(2);
}

public static void foo(int x){
print Parameters;
foo(Parameters+Parameters);
}



For the weekend: debug class declarations, make 'new' work, control structures, further operators for built-in types.

Telastyn

Telastyn

 

Tangent: Milestone 4 - Methods

Its nice to be able to get back to work after being sick for a few days. Very productive both at work, and at hobby work today. Tangent's Hello World now works:


public static void main(){
print "Hello World";
}




Not very impressive, I know. And if I actually had dotNet importing working better, it'd be not very dissimilar from C# Hello World. That is perhaps part of the idea. Keep the syntax, use the libraries, change little bits here and there to allow the mix-in behavior and better function as object usage.


And while it doesn't look very impressive from the 'screenshot' realm, it was a headache and a half to implement. The problem came perhaps from my milestone ordering. I decided to do methods before classes, thinking that "oh, I already have the block parsing done; just add the header stuff and poof!". A nice reminder that poof should cause a violent physical reaction whenever it comes near designs/plans.

Basically, Tangent works like C#. Everything goes into a class. Global methods are fine, but under the hood they too just end up in a static, global class. So under the hood, I needed to implement adding members to classes (which umm, wasn't supposed to be until milestone 5).

And then I kinda realized that all the nice type interaction behaviors that I'd made and tested worked on fully made types, and returned new ones. And a number of the mix-in behaviors are defined in terms of those interaction behaviors... which lead to 'how do you know if it's fine to mix these two, when they're only 1/2 built'. And once that could be solved, it mostly involved keeping nice sane references to types to be filled in later... which is impossible when the code for that returns new ones.

And I need to better test the stuff that's there; make sure that parameter creation works right and eventually non-static methods. Also, Type Expressions aren't dealt with properly as return or parameter types... and overloading/mix-in of all sorts currently just punts. and I need to get around to reworking the actual method invocation (it doesn't nicely support/respect recursion/multi-threading). And I should probably implement the basic operations for basic types so the guy I bounce ideas off of gets off my ass about them [grin]

But hello world works. Woo.

Telastyn

Telastyn

 

Tangent vs Scala

Doing some research today on some stuff for Tangent (because the amusing sites have been blocked at work), I found that the stuff I've been terming mixins for Tangent are more appropriately called traits (since they may hold state). That yielded a nice ton of more info and links.

The most important of which was reading through the documents for scala. The three main features (mixin inheritance, custom operators, proper function behavior) I was dealing with in Tangent are already there and implemented in a mildly stable language. Tangent is prefix & infix only, scala is postfix & infix only; Tangent is off of C#, scala off of java.

But the parallels are kinda disturbing now that I've found it, and kinda annoying that I'd not found (and nobody pointed it out) earlier. There do seem to be some distinct differences. I think scala still requires explicit inheritance of the mixin/interface, and it's very functionally oriented; which I don't care for.

(at the moment. I and I'd argue many programmers, can't make effective use of functional benefits. Making it at the forefront of the language is (imo) of questionable benefit until that changes)

Tangent will allow functional style where it's beneficial... maybe not so efficiently or as natural to folks more familiar with functional languages. Something to review once I get some skilled people fiddling with things. Some 'match' style syntax for method overloads seems like the easiest addition that would provide significant benefits in the ease of use department.

So scala looks interesting (though it seems to suffer from functional languages' "succinctness" obsession *bleh~!*). Something for me to look into when time allows.


Tangent work will in the mean time continue. Function declarations continue. The parsed tokens are broken out and dealt with in turn now. Next is doing the delayed name resolution for types, and then unioning the constructed bits into the object tree.

Telastyn

Telastyn

 

Milestone 3 and then some.

I hit milestone 3 a few days ago. Local variable declaration works (for ints at least). Initialization, default values, scope.

Tonight I had free so worked quickly on milestone 4 (method declarations). About 45 minutes and I had the grammar setup and the first testcase:


public static void foo(){
new int x = 6;
print x;
}
int bar(int,int);
private delegate string baz(string moo);


And parsed correctly the first fly through. Quite satisfying to say the least.


In other news my work today decided to implement some nice URL/IP blacklists. Bye bye penny arcade. They even blocked my local pickup hockey league's site, but not gamedev. (clearly work appropriate forums...) And in a move of amazing genius, they blocked the site that the block page links to for more info!

I can't imagine what their goal is. Employee morale is already at an all time low, what with the weekly 'so and so is off to pursue other opportunity' emails, the universally horrible last round of reviews (3% for all departments!), the tight-assed dress code, the joke christmas 'bonuses' (cheap, useless company branded crap), the intrusive IT spyware, the shit benefits...

It's not as though a company has to suck to move to Miami.

Managed to update the resume. Now to find the motivation to polish and publish it. ...or at least find the skills to guarantee gainful employment.

Telastyn

Telastyn

 

Tangent: Local Variable Roadblock.

Local variable declarations quickly got complex. First, I underestimated the work involved. Second, I am an idiot (and made a poor design choice). Third, I got greedy (and remembered I should probably implement op-assign properly).

The good news is that I'll end up with a lot more done than I expected at Milestone 3. Type expressions will be there for local variables (and reusable for other constructs). Type expressions are important in Tangent because they allow composition (and a few other operations) without explicitly compositing the damned types. Also done are an operation to select compatible methods from a group, 'intersect' the common interface out from two types, and a tupling operator.

Also done (once I get to implementing op-assign...) will be pretty full initializer support, including hardcoded defaults (until I get around to implementing a 'default' operator). But op assign is tricky. And doing it nicely will likely require some overloads and/or generics that aren't really there yet.

Just one of those things that was easy when I was looking to just compile to C# rather than interpreting things. Now interpreting things, it's surprisingly difficult...

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!