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

About this blog

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

Entries in this blog

 

Tangent: Local Variable Declarations

I added in local variable declarations to the Tangent syntax.

Unfortunately they don't quite line up with C#, mostly because types can act as unary operators. type foo = bar; becomes a bit ambiguous if type can act as a unary op. Is it a variable declaration, or is foo supposed to be a param to type, and the result assigned? Almost always the first, but requires a bit of work /lookahead to determine that.

So you'll need to use 'new' to disambiguate them (or perhaps 'local'):

new int x = 42;

Not fantastically ideal, but adjustments are not unexpected when making relatively far reaching changes in a language. The parsed bits don't work yet. That's for tomorrow or the weekend.

Telastyn

Telastyn

 

Tangent: Milestone 2

Milestone 1: ~3 months
Milestone 2: ~3 days

(and another 4 weeks making things work beyond my one arbitrary test and after further use reminds me that I am an idiot)

Tangent now parses and interprets literals:


// a = "moo." above
// print defined in journal entries below
{
print "bleat. " 3.14 " " 42 " " 'X' " " a;
}


nicely prints

bleat. 3.14 42 X moo.


Though the use of space literals makes that largely horrific looking.

Anyways:

-true/false/null work, though actual bool objects not yet implemented.
-Other literals contain values, but only 'ToString()' implemented for them yet.
-char literals work.
-int literals work so far as int.TryParse does; that is... u/l suffixes fail.
-real literals work so far as decimal.TryParse does; that is... d/m/f suffixes and exponent notation fail.
-string and C#-style verbatim string literals work, though escape sequences are a little buggy at the moment.

I'll probably flesh out some of the basic types (simple operators for int/real/string) and then it's on to local variable declarations.

Telastyn

Telastyn

 

Milestone 1 (for real this time)

After 45 mins more debugging:

And it turns out the instance binding was working fine and the return statements were also working well. The problem was that I was using the instance of the method for all that, but executing the code from the template. I know that doesn't really make sense.... I'm pretty certain this fugly implementation of method instances is counter to how it's supposed to be done and going to cause memory/runtime issues later. But that's later. Gobbling methods now work, meaning I've (finally) hit full Milestone 1.

Unfortunately it's a small tidbit in the larger scheme of things, despite all that's been done. I'm still manually defining classes and method declarations. .NET importing doesn't work. Literals don't work. There's no entrypoint mechanism, no assembly references, no new statements!

So a tentative roadmap:

Milestone 2 - Literals. Support for all .NET literals is ideal. Support for unescaped strings, chars, decimal integers (no hex notation), and floats is sufficient.

Milestone 3 - Local variable declarations in code.

Milestone 4 - Method declarations in code.

Milestone 5 - Class and class member declarations in code. 'new' should work.

Milestone 6 - Support for partial classes and operator overloading. Look into the need for a C++ style functor definitions. Entrypoint handling is likely done here.

Milestone 7 - Class Generics in code and operation requirement syntax. Type parameterization in the interpreter.

Milestone 8 - Method Generics support in the interpreter. Constraints in syntax.

Milestone 9 - Syntactic sugar for properties so that they behave like .NET properties properly. Mostly this is for the 'getter' part of properties since setters are just an overload of op= (though that requires method generics)

Milestone 10 - Indexers.

Milestone 11 - 'using' support and assembly references.

Milestone 12 - Support for Tangent objects in .NET Generics. (which will likely require some dirty hack)


... and that's about as far out as I've thought through. This is still missing some fairly major things; the lock keyword, 'using' blocks, exceptions, yield, anonymous delegates, lambda stuff (unlikely to get done), linq stuff.

Plus I'll need to improve error reporting, some simple standard library stuff, documentation. Which also assumes that this thing doesn't crash down under its own weight somewhere in there due to the ambiguities being too prevalent or the compilation being too slow or the interpreter just running dog slow.


Still, I've got most of the key things working that I wanted to research. The static duck typing works pretty well. The operation inference works nicely (though might be too ambiguous/slow once complexity ramps up). Arbitrary operators work (though might place too much burden on the program designer to work well). Method selection on multiple parameters needs tested more, but should work nicely. Methods/operators and method groups can be passed around wily-nily.

Now to get the rest of this puppy working so I can see if this crap actually proves useful for programming non-trivial projects.

Telastyn

Telastyn

 

Tangent: One step forward, one step back (again)

So I tracked down the problem with the gobblers.

I wasn't nicely keeping track of wildcards' (paren statements) effective type, so they kinda got dropped. [reworked wildcard class to include that, and cache results]

And then when I un-flattened the token stream, I wasn't checking if the token was already un-flattened and passing its children along. [edit unflattening code]

Then I ran into a problem where it wasn't clear with the way the AST was formed what the operation was supposed to be. [rewrote AST nodes to include operations]

Which exposed some sort of bug with instance binding or return statements (I can't quite tell which yet) where the return of a method is being assigned to the slot of... some other method's return slot (or some temp slot; I can't quite tell which yet).


So yeah, a lot of plumbing amongst interpreter internals to track down vague failures with seemingly innocuous code. Whole lotta work for not much (visible) progress. And despite not being terribly clever in my design, a whole lot of:




ps. Indiana Jones? Pretty crappy.


ps2. Malibu is tastey.

Telastyn

Telastyn

 

Tangent: One step forward, One step back

After the (admittedly unsurprising) partial failure of my car's brake system and the resultant hours at the garage, I got some Tangent work done.

The primary work of the day was refactoring the identifier resolution/overload-ambiguity checking into separate bits. Part of the problem of "just make it work" is that the working bits tend to be sticky. This was one of those cases where I rushed to make it work, and now had to go back and do it more right. In this case, it's because I failed to account for methods returning method groups. The returned group never got fanned out into its possible permutations so the 'gobbler' style methods I want didn't work.

(That is, a method group where it 'gobbles' a parameter and returns itself again to gobble more or void to terminate. A mildly more functional approach to varargs.)

And the part that did the syntax analysis wasn't able to nicely interact with the part that did identifier resolution because the later was doing too much. That's all done now, but I managed to break the little I did have working.

discard print (Parameters); is parsed in as discard(print); Effectively doing nothing. It's probably some simple/dumb mistake I did during the refactoring, but I'm not quite sure where. And it's a little too late to be hunting through compiler internals to see which of the three change points caused this particular symptom... But once I do fix that tidbit, gobbling should work now. Leaving me free to continue on to method declarations.

Telastyn

Telastyn

 

Tangent: Milestone 1 and mechanics.

I see milestone 1 on the horizon. Tangent now compiles raw source into code-dom-ish sort of structures which can be run. Type declarations are still hardcoded, and method groups have a bug where they don't get default initialized. Once that bug is fixed and I see code work I'll hit milestone one. Another 30 minutes of work or so, but I'm tired and feel like talking (to nobody as far as I can tell really).

[edit - 6 hours later]: Okay... 30 minute estimate; bad idea. Still not working due to some minor catastrophes with method groups and blocks.
[/edit]

But I'd like to take a few paragraphs and talk about some of the mechanics of Tangent which make it different (for better or worse).


The most immediate difference between Tangent and popular languages is its type system. Tangent types are statically declared like C#, Java and their kin, but do not follow a hierarchical inheritance structure (by default). A Tangent type is considered a sub-type of another if it supplies at least the required interface. Thus it provides a kind of static duck typing. Inheritance itself is provided via Mixin style type composition. Methods, Method Groups, and even Operators are all proper objects and can be passed around as such.

The second major difference is how code is parsed. Tangent follows the standard lex->parse->syntax analysis pattern for modern compiled languages, but defers all 'block' parsing to the syntax analysis step. It puts off dealing with the actual executable bits of code until the type declarations are dealt with. This is to allow custom operators.

Tangent allows operator declarations that are standard identifier names. In 'Ogre kills Player', kills would be defined as a method taking the suitable types. A Tangent statement is thus a series of identifiers (and a few other things such as member access tokens, standard +-*/... operators, parens) which results in void. Yes, that means just 2+2; as your statement yields a compile time error; sorry. The block parsing step then takes the flattened stream of identifiers and uses the type info to construct a more traditional abstract syntax tree.

The idea being that having custom operators will allow a lot more readable and thus maintainable code. Coupled with the duck typing it should also provide a better mechanism for coupling data with the operations valid upon it. An operator can require data match a particular interface without explicitly inheriting from a base type (and as such be able to work on classes in a different API with fewer adapters).


Though I am not sure how these things will quite work in practice. I think that being able to natively use .NET objects/classes will be a boon. I think that the functional capability to pass around and return methods/method groups/operators will be a boon to using functional programming style for algorithms and the sort where that really shines. I think that the type system provides many of the compile time check and explicit definition benefits that static typing offers while also allowing more flexibility where it's useful (interfaces and type composition).

Though I can also foresee some realistic cases where compile times are too long, or execution performance too poor, or statement ambiguity too common/annoying, or operation conflicts too common/annoying or the operations simply too vague about what the actual code is doing. We'll see in 3-4 more milestones when I get this puppy to the point where I can get some unbiased 3rd parties writing code in it.

Telastyn

Telastyn

 

Tangent: Creeping towards hello world.

Work on Tangent progresses more rapidly now. I have the infrastructure in place and tested... a little independently. But now I have 5 disparate parts (parsing, tokens, type inference, types, code-dom-ish stuff) and none of them work together. They're well designed though, so the steps of stringing them together is fast.

I've even got dotNet invocation working, and hard-coded a 'print "foo" to screen' test which uses the underlying bits of the language. My next milestone is taking a block of code in Tangent and running it. Hard-coded type declarations, no assignment, no literals, hard-coded data...

This actually:

class int{
// hard-coded from dotnet-int's storage and method;
public ToString(){...}
}

class string{
// hard-coded from dotnet-string's storage and method;
public ToString(){...}
}

interface AnythingWithToString{
// hard-coded duck-typing interface
public ToString();
}

void discard(Any x){
return();
}

print print(AnythingWithToString x){
Console.Write(x.ToString());
return(print);
}

void print(AnythingWithToString x){
discard print x;
}

int a = 42;
string b = "moo.";

// And then I want this parsed and executable with the above declared.
print a b;




Which by all testing will work fine and print '42moo.' to the console. Plus it's a simple example that shows a little of the language features like duck typing, and the user-defined operators. Now it's just a matter of stringing those bits together and making it work.

Telastyn

Telastyn

 

Tangent: Operator Constraints & cd review

One of the kinda awkward problems in C# is the lack of a 'numeric' constraint for lack of a better definition. If you make a generic T, you can't do a+b since they might be objects... or something else where that doesn't make sense. And you can't provide a type constraint since int/float/etc don't have a common base type where the operators are defined. In general, the ability to say 'Type T for this generic must have T+T -> T' doesn't exist.

Since Tangent is going to be fairly operator and interface heavy, I'm highly motivated to provide some mechanism where that type/operator inter-relation requirement exists. I am unfortunately an uncultured swine, so am likely re-producing an existing solution (badly).

The current solution is (I think) similar to some of the Type Class stuff in Haskell (but not knowing Haskell, or finding example/documentation which is not too simplistic or too advanced -- I can't say for sure). And it's quite similar to declaring an abstract method for a class. When used as a variable or type constraint on a generic, the existence of that signature for the specified operator is checked at compile time.


public class Addable{
public this @plus(this,this); // + is an alias for the global method
// 'plus'
//
// The @ symbol is the tentative syntax
// for an absolute identifier path/name.
}

public class foo{
public static T add(T lhs, T rhs) where T: Addable{
return(lhs+rhs); // this is fine because the constraint says it will be
}
}

foo.add(5,6); // fine, 2 ints return an int.
foo.add("moo",4); // error, string and int don't match the signature.
foo.add(x,y); // error if x+y is undefined




And since operators are overloadable and types are ducktyping, foo.add will behave nicely for anything that overloads plus properly, regardless if that anything inherits directly from Addable.

Seems spiffy. Hopefully it doesn't cause loops on checking or fragile stuff at runtime if people are bad about operator overloading or annoying stuff if I'm too stringent on disambiguation. We'll see once I get more done/working.


In other news, a CD review!

After a quick poll, everyone in my car seems to agree that they like Evanescence. They can't really agree on anything else they like, and they're all kinda fed up with listening to them. So last week I pooled up some cash, went to Amazon and did a 'similar to' search. Checked some youtube clips and generally hunted about for bands I've never heard of under my rock.

The (decidedly European) results:
God has a Plan for Us All - Angtoria
The Heart of Everything - Within Temptation
Century Child - NightWish


And all in all, I'm pretty impressed with the suggestions. The quality is good, and the sound is quite similar. It's kind of amusing to listen as each of the bands seem to have a separate strength.

Angtoria - The darkest of the three CDs, also has a cover of Kylie Minogue's Confide in Me. The lead singer is probably the strongest of the three, though the backing band is a little sloppy and the compositions are just lacking in some of the variation and catchiness. This is their debut CD though, so that is likely to improve a bit. A good CD, not great with some promise for the future. 7 of 10.

Within Temptation - The most mainstream of the three CDs. This is a solid cd. Every track is good to great; none of those 'bleh - skip it' dead tracks on most CDs. The sound is fairly uniform throughout, though the songs do distinguish themselves. The band isn't hugely outstanding, as is the singer, just consistently above average. The lyrics aren't as vivid or creative as Evanescence, and the performances aren't as emotional. They are though a bit more technically sound and consistently good. Still, great CD; highly recommended. 9 of 10.

NightWish - A popular band across the pond, Century Child is an older CD before they changed singers. These guys are the most 'metal' of the three, and have the most operatic sound to the vocals. Unfortunately the operatic sound isn't very good. The band is very good. The compositions have a fairly high technical difficulty for semi-mainstream music and the performance really nails them. No sloppy playing, no noise for the sake of noise. The songs vary a bit, and some miss. The ones that hit hit well though. Included is a symphonic/gothmetal cover of 'Phantom of the Opera'. All in all pretty good after some re-listening. 7 of 10.

Telastyn

Telastyn

 

Tangent: Generics 1

Generics are a pain to implement. Or rather generic methods (as most languages implement them) complicate matters quite a bit. I had one implementation that worked, but made generic methods infeasible as other languages implement them (a simple replacement akin to C++ style templates for type expressions only). I thought of another design which would've looked like:


public T foo(TypeConstraint lhs, List rhs);


Which in my estimation is pretty straightforward to implement. You've one source for the type, and the test to ensure the two arguments are the same type follows from that. I bounce ideas off a guy I know who's also working on a toy language and in his estimation this idea was largely crap. The replacement was icky and would lead to problems later, the procedural style generics were unweildy and would lead to confusion or design restrictions later.


Enh. I don't see it.

Still, I've got design #3 done now and working for basic type binding. It doesn't do straight replacement, which should allow for limited type inferring like generic methods need.

Next steps: More unit testing, Generic method type inferring, making the parsed type parameterization do stuff, and operator constraints for generics (ie. numeric supports T +(T,T); T -(T,T) etc... similar to haskell stuff)


[edit:]

The Type inferring code is written and appears to work for at least the simple cases (foo infer foo == foo and foo infer (type identical to foo only not generated via parameterization) == same)

It kinda sucks that I had to eventually implement some form of pattern matching. This is specifically the thing that I wanted to avoid with the rejected design #2 above... Maybe if I'm feeling ambitious Version 2.0 will actually use the pattern matching in some way more interesting than generic methods.

But enough of that. More unit testing, more parser linkie, more making stuff work now. Generics should allow for the operation constraints and the Type Masquerading that I'd like to research.

Maybe one of these days I'll actually do this 'get it done' stuff I keep telling myself... :/

Telastyn

Telastyn

 

Tangent: Further tokens

Progress on Tangent continues slowly. I added support for member access and parenthetical expressions to the code that takes a series of tokens and builds a sane abstract syntax tree.

(where string a,b,c and (string,string) tupleab are defined)

c = a + b;
c = string.concat tupleab;
c = string.concat(a,b);


All now effectively generate the same AST. On the list is tying that token code to the parser, so I can go straight from source to AST and generating useful stuff from the AST's. And sometime I'm going to have to rework the type parameterization stuff. But I must focus on getting something working. Actually concat-ing a string or adding some ints and dumping to console. No compilation needed; a hacky interpreter will be fine. Even starting with only the first version to avoid instance methods for the time being will be fine.

Telastyn

Telastyn

 

Tangent: Parsing

I have today off and completely for my own use. I'm not a big fan of vacations, and using my time for just a personal day is so nice.

This morning then was work on Tangent. Whimsy pushed me to work on simple parsing for expressions. Not quite sure why since it's not the next step in the work (though it's probably because the actual next steps are more daunting).

Anyways, the lexical stuff is using the Lexer stuff for C# I have from an existing project. The grammar for things is currently very, very simple:


block := { statement* }
statement := element* ;
element := identifier|member_access|type_parameterization|paren_expression|operator_shorthand|block

identifier :=
member_access := .
type_parameterization :=
paren_expression := ( element* )
operator_shorthand :=


Very simple. Most of the work will be done later taking the stream of elements and organizing them into a sane order of operations. There is a slight problem with this compared to more traditional parsers, mainly the type parameterization.

Something like Dictionary foo will need to be Dictionary foo. Mildly annoying, but multi-parameter generics aren't terribly common and I can't think of a better way to keep things intuitive-ish, while also disambiguating the angle bracket usages.

And I don't suspect things will get much more complex for code that isn't type declarations.

Telastyn

Telastyn

 

Tangent: Expressions

One of the key desires which evolved with my toy language Tangent was to allow fairly arbitrary operators. As long as a method met a certain signature, the language should allow it to be used as an operator.

This moves a lot of the expression parsing from the parser proper into the syntax checking portion of a traditional interpreter/compiler setup. It is also one of the most dubious parts of the design as far as me being actually able to implement it. Also it was one of those things that cried out to be one of those cases where something is a lot more difficult than it seems or a lot cooler in design than in use.

Anyways, I have at least the simple case completed; with the most daunting use case. Take a list of arbitrary identifiers, and figure out how they make sense. The test case is simply: c = a + b; where a,b and c are all strings. Assignment has a definition for void(string,string) and plus has a definition for string(string,string).

This is an interesting case since my original idea was going to be 'read from left to right, use parens otherwise'. That is a lot easier to implement, but would require things to be c = (a + b); which is annoying. So the parsing for this goes:


(c = a) + b; // oops, that makes no sense.
c = (a + b); -> (c = (a + b)); // good.


The trick to making the processing sane was to consider it just like any other parser. Do a simple recursive descent. The only differences are that type info is actually available, and the target needs to be continually adapted as the partial parsing attempts go through it.

and the standard screenshot:

c = a + b;
Identifier Paths:
---

TestStatement
Locals
c
---

assignment
---

TestStatement
Locals
a
---

plus
---

TestStatement
Locals
b


assignment
c
plus
a
b



I'm not sure if I'll be able to stick with this sort of order of operations. I think with an IDE tool it will be okay. Otherwise the left to right strict style might be required.

Next on the list is expressions with method groups, parentheses, and turning this output into something usable.

Telastyn

Telastyn

 

Method Groups in Tangent.

(true(),false()) select bool() == Ambiguous - True:True
(true(),false()) select false() == false() - True:True
(true(),false()) select true() == true() - True:True
(true(),false()) select null() == Undefinded - True:True
(true(),void(true)) select true() == true() - True:True
(true(),void(true)) select void(null) == void(true) - True:True



My little toy language is coming along. Tonight's work? Starting in on method groups. The few little unit tests from above are checking on the function that does type picking/resolution (rather than the actual dispatch). As you might be able to see, it will be smart enough to pick the right one based on return type if the requested return type sufficiently limits the options.

For now at least. I suspect that I might run into troubles/conflicts when actually getting the dispatch going. Once this gets done I should be able to start importing basic/test .NET types. Once that is done, I can start on the really dubious stuff (making the syntax analysis smart enough to allow user defined operators).

An interesting side effect of how I did things allows this sort of thing to work:


delegate int foo(int x, int y);
foo IntOperator = delegate(){return(42);};


You'll still need to call IntOperator with 2 ints, but the assignment will work and you'll always get 42 back. I'm not sure if this has some advantage or if it'll prevent useful error reporting later on. Kinda weird, but for now it's in.

Telastyn

Telastyn

 

...

WARNING: There may be naughty words that follow. You've been warned.

I've had a little bit of a rough time getting to where I am. I made the mistake of not studying computer science in college. Then I made the mistake of going to college early (before I was ready as it turns out), and did not finish. Thus I spent 7 years working my way up from phone monkey to sys-admin to QA. Then I got to spend a year unemployed as a wonderful complication of these mistakes.

After that though, I applied for an entry level dev job posted for about the 60th percentile (according to salary.com) and 15k off my previous position. Got an offer for 15% less than that, and took it. After all I'd not proved anything, and was hurting for cash, and realistically I had no good way to evaluate my skills. Might as well start at the bottom, pickup some missing skills, some years on the resume.

And things went well. I picked up Java and SQL better than half the existing team, got a better grasp of cvs, and spent a good while becoming acclimated to the huge system the team works on/with. I turned around a $1m/year client from a clusterfuck, developed the financial processing for our new setup, added creditcard processing to our site, trained up 3 new guys, about 4 dozen smaller tasks, and about 1/5-1/3 of the bug/tickets.

It was about 18 months in and review time rolls around (first one due to a quirk of the company HR policies). Great work, you've really helped take care of things with no oversight, you've picked everything up so quickly... 5 of 5. Astounding! I'm one of those 'straight B' guys. Work just hard enough to do well, but not hard enough to really excel. A 5 on the performance review is just unheard of. 'The checks will be out at end of month.'

So it's end of month. I am disappointed. To say the least really... more of a speechless, vitriol spewing rage machine. So a 5 on the performance review at this place merits, that's right... 3.5%. Barely INFLATION! (depending on who you ask). It is a fucking joke. Our site has about 250 people and we get, I kid you not, 1-2 emails a week about 'so-and-so has left to persue other opportunities'. I mean, I just thought it was the crappy benefits or super shitty christmas 'bonus' (crappy stuff with the company logo on it) or generally mind-numbing work (your standard 'fiddle with stuff in a DB' operation)...

And it has to be bad business! It has to cost like $40-50k every time someone leaves. It takes 3-6 months to train them into something useful because of the system complexity (10-25k in salary alone + benefits + facilities + time taken from person answering questions + $$$ lost by screw ups + $$$ lost by people waiting on the new guy + time/effort to job-post/screen/interview). Why in the world would you skimp and pay the entirety of the programmers inflation or worse?!? (remember, I got a 5. Others got a bit less than 3.5%)

You're going to lose 1/4 of the team or so (especially since there's 3-4 other guys in my situation of being 'entry level' because of some schooling or inexperience flaw) and are going to spend $40-50k a head to replace them.

Fucking stupid.


So basically I'm owner of a payment processing webapp, a dozen smaller apps, the head IT guy for a half dozen 7-9 figure financial processing clients, and the #2 guy to go to ask about any of the backend financial processing stuff... for 50th percentile salary of an entry level programmer.

To say that I am not pleased is an understatement.

Telastyn

Telastyn

 

Tangent continues.

In honor of this little tangent I'm taking in doing something useful, I've decided to name the little hobby language 'Tangent' until I can think of something better.

So far I've accomplished quite a bit. Unfortunately the vast majority of that is proving that I am indeed an idiot. The rest is mostly learning enough MSIL to get a vague idea of what's going on and how to use ildasm. Since I'm an idiot, I'm not really doing much planning or anything sane like typing out the bnf ahead of time.

As such I went through 3 quickie underpinning prototypes before getting something vaguely manageable where I'm not trying to define something in terms of itself (methods are the big culprit here really). A fairly insightful reason why first class methods don't exist in a few places; they're kinda weird and sucky.

Since the core idea is based around the type-system, the prototype that worked was based around doing that first. It's got a good start so far. Operations for member/type equality and greater/less than are there. They're not well tested since I'm going to be relying upon .NET types to be the primatives/storage. Something to convert those types into TangentTypes is not done yet so it's pretty much impossible to declare members at the moment (since a member wants a type, which wants a member which...)

And I'm sure there's going to be tons of gotchas in that conversion process. Like string + string isn't actually string.op+... wonderous little tidbits you learn via msil.

and erm, unit test screenshot!

Void False:False
Void False:False
Any True:True
Any Void - False:False
Null Void - False:False
Null False:False

Void > Null - False:False
Void > Any - False:False
Any > Null - False:False
Any > Void - False:False
Null > Void - False:False
Null > Any - True:True

Void False:False
Void False:False
Any True:True
Any Void - False:False
Null Void - False:False
Null False:False

Void >= Null - False:False
Void >= Any - False:False
Any >= Null - False:False
Any >= Void - False:False
Null >= Void - False:False
Null >= Any - True:True

Void == Null - False:False
Void == Any - False:False
Any == Null - False:False
Any == Void - False:False
Null == Void - False:False
Null == Any - False:False
Any == Any - True:True
Void == Void - True:True
Null == Null - True:True

Void != Null - True:True
Void != Any - True:True
Any != Null - True:True
Any != Void - True:True
Null != Void - True:True
Null != Any - True:True
Any != Any - False:False
Void != Void - False:False
Null != Null - False:False


Telastyn

Telastyn

 

Tangent 1.5

The whimsy to knock together a toy language is still around. It only seems to grow looking into other languages like F#/Ruby/Python which all make me want to vomit and/or kill more or less. F# is the least offensive, probably because it's the most alien (and the alien things make sense).

But I've spent my commutes pondering what I want, how to implement it, and if any of the ideas are self-contradictory or otherwise problematic. I've worked through enough of the ideas to start jotting down some of the requirements into my notebook and look into getting some code into the editor.

The original motivation was from some of the threads discussing composition and program design in Game Programming. The entire process of taking disparate parts of code and gluing them together is harder than it should be. Since that is increasingly what developers actually do, it should be easier.


So there are three main points where I think improvements can be made.

1. Inheritance sucks.

When the conceptual goal for something is to take module A,B,C, and maybe D and glue them into some nice object where they all work together, having single inheritance is conceptually askew from that. Having to translate from one thinking to another is a bit of work and can lead to errors. Multiple inheritance has plenty of its own problems which only adds to the work the programmer has to do.

Loose typing circumvents a bit of these problems, but introduces a number of others. I like statically typed languages, and think that the benefits of them are almost always advantageous for non-trivial applications.

So I'm going for mixin style inheritance. Allow for multiple inheritance (or at least ease the programmer work in making multiple inheritance into chained single inheritance), but mitigate the problems while still following a statically typed feel for things.

2. Functions are important

(and by extension, operators)

Using C# for a while as my hobby language has led me to kind of hate delegates. Not delegates themselves, but the shoddy implementation in .NET. A programmer will have to work with functions, and perform operations with them. Adapting a function to a different signature is one of the key 'glue' operations a programmer needs to do. Make it easy.

And heaven help you if you need to use the signature to parameterize anything...

3. Rigid Typing is... Rigid

This is sort of an extension of #1. Interfaces in C# suck. They require explicit inheritance to assign an instance to a variable (and by extension a parameter). When you're sitting there with 3+ modules, there's bound to be methods just sitting around that take interfaces (if you're lucky) as parameters. Out comes the glue and a class or two are inherited again to explicitly adapt to the interface; obfuscating what's going on and require a good bit of work from the programmer that isn't necessary.

Duck typing helps this greatly, but can lead to a few problems. It often just checks that the stuff that's used is in the type it's dealing with. Not so good when the module you're including changes it's signature or you swap in a new concrete class that doesn't quite use the same stuff. Or it uses type inference which obfuscates what's going on (especially when everything else is statically typed).

Especially with the mixin stuff, I want to provide psuedo-duck typing. Simply loosen the standard casting operators/implicit conversions to do the capability checking between types, rather than between usage and type as normal duck typing would do. So the function can specify Ifoo as a parameter type and the argument coming in just needs to satisfy the interface, not necessarily inherit from it.


Not that it matters too much. I still lack the CS-fu to take on such a project, let alone make it good/suitably fast. Still, it should be a learning experience for the very least what I don't know. So I'll work on it a bit as time allows. Hopefully enough to sate my whimsy so I can get it back onto useful stuff.

Telastyn

Telastyn

 

Adventures in F# Part 1.5

Holy crap, a comment!

In other news, I dabbled a little more with F# over the past days. The BigInt stuff seems to be missing a lot, and the intellisense on stuff seems wonky. For list/List at least, it tends to oscillate between three visible types; only one really containing the useful (if sometimes terribly named) bits.

I found a few more pattern matching examples ( (n::_) for something starting a list for example) which make it a little niftier than sugar for if/else blocks. Still a disappointment. The style does though lend itself very, very nicely towards algorithm implementation. I got half through an implementation of the quadratic sieve before I realized that BigInt kinda sucked.

So I went on to a traditional interview question that I've always seemed to have trouble with: "Reverse a string in place."


let rec reverse a =
match a with
| [] -> []
| _ -> (reverse (List.tl a)) @ [List.hd a]







Still fairly newbish with the language, but it took 1 shot and about a minute. No BS pointer arithmetic; straight-forward and easy to remember. Now if only this tl/hd/car/cdr bull could go the way of "let's see how much we can do in one line of code!" perl.

God I hate that stuff.

Telastyn

Telastyn

 

Firewalls

My work decided over the weekend to lock down everything outgoing except port 80. This is the same place that requires "new" passwords every month. And requires papers in-triplicate before assigning people laptops.

Because clearly any determined internal attacker won't think of printing anything out and walking out the front door.

Telastyn

Telastyn

 

Adventures in F# Part 1

I finished my first F# app in about 2 hours. No feedback at deadline and it's been pushed off the main page. Alas.

I am a little disappointed so far. Pattern matching, which seems to be one of the big things promoted as functional stuff to learn seems at face value to be not much more than nicer syntax for if/else/switch blocks. Not anything particularly new. I assume I'm just missing some nuance or situation where it is balls out fantastic.

The MSVS integration is okay. Syntax highlighting is sparse, its not really polished, and it wants to label stuff with weird errors a little prematurely. Intellisense is a little quirky, but works pretty well even with type inference everywhere. Most importantly, it works with little focus to the underlying bits and with consistency. Now if they could just make the intellisense case insensitive like it is with other languages and I'll be happy.

The language itself is okay I suppose. I dislike the lowercase function names, and example's functional drive to cram the most function into the least amount of space. x -> x*x is nice and direct, but more complex things quickly get out of hand. I was a perl nut for about a year, but executable line noise is not the way to go...

And re-pasted from the thread for posterity, F# part 1: Print Lines of Code in a MSVS solution (minus exception handling).


#light

open System.IO;

let IsSourceLine (Line:System.String) = Line.Trim().StartsWith(")

let GetLineCount FileName =
File.ReadAllLines(FileName).Length

let GetSourceFileFromCompileLine Line=
let words = String.split ['\"'] Line
List.nth words 1

let ProjectLines ProjectPath =
let BaseDirectory = Directory.GetParent(ProjectPath).ToString()
let ProjectFileLines = List.of_array (File.ReadAllLines(ProjectPath))
let CompileLines = ProjectFileLines.Filter IsSourceLine
let SourceFiles = CompileLines.Map GetSourceFileFromCompileLine
let SourcePaths = SourceFiles.Map (fun x -> BaseDirectory + "\\" + x)
SourcePaths.Iterate (fun x -> System.Console.WriteLine(x))
let SourceFileLineCount = SourcePaths.Map GetLineCount
List.fold_left (+) 0 SourceFileLineCount


let IsProjectLine (Line:System.String) = Line.StartsWith("Project")

let GetProjectPath Line =
let words = String.split [','] Line
let second = List.nth words 1
let second = second.Replace("\"","")
second.Trim()


let LinesPerSolutionFile SolutionFile =
let BaseDirectory = Directory.GetParent(SolutionFile).ToString()
let Lines = List.of_array (File.ReadAllLines(SolutionFile))
let Lines = Lines.Filter IsProjectLine
let ProjectFiles = Lines.Map GetProjectPath
let ProjectPaths = ProjectFiles.Map (fun x -> BaseDirectory + "\\" + x)
//ProjectPaths.Iterate (fun x -> System.Console.WriteLine(x))
let ProjectLineCounts = ProjectPaths.Map ProjectLines
List.fold_left (+) 0 ProjectLineCounts

let Main =
//let args = Set.of_array (System.Environment.GetCommandLineArgs())
let args = [@"C:\src\Csharp-moeClient2\Csharp-moeClient2.sln"]
let LineCounts = args.Map LinesPerSolutionFile
System.Console.WriteLine()
(List.combine args LineCounts).Iterate (fun (path, lineCount) -> System.Console.WriteLine("{0}: {1}",path,lineCount))

//do System.Console.WriteLine("Foo.");
//do Main

do System.Threading.Thread.Sleep(5000)



Telastyn

Telastyn

 

Tangent.

Two weeks have gone by, which means my whimsy as directed me elsewhere. No wonder I can't get anything done... I got another drive to do programming language development, which is dumb since I didn't have the follow-through to get the plain C# parser working fully to extend that. Also dumb since I don't near have the requisite knowledge to make one that works, let alone well.

At least I'm smart enough to know that these days rather than foraging onward to my death. As such I'm going to try to learn during this 3 week spurt of motivation before whimsy comes again. I've browsed through some articles on set/type theory, traits, mixins, some of the functional language type systems... and think it's due time to pickup a new language.

I'd been looking at Python for a while given its high recommendations by respected members of the forums, but little about it seems appealing. Everything I've seen and heard makes it sound like slightly beautified perl. Bleh. Plus I don't have that much motivation to work beyond my admitted bias against mixing code format and function.

Plus it's not 'different' enough to be of primary benefit I think. So I'm going to work with F# for the coming weeks. I've not seen abject arguments against it, it has enough IDE and library support so it's not too different or motivation sapping. Plus I hope that the interop possibilities will allow it to avoid the impracticality argument leveled against its functional kin. I dabbled with scheme a little, but the poor IDE and impracticality quickly deterred that path. Let's see if this doesn't provide a more palatable option that I can leverage into more esoteric things. Or if I don't get sick of it and give python a shot despite my misgivings.

Telastyn

Telastyn

 

The March of War! (eat snackysmores...)

Or progress anyways. My long weekend of free time comes to a close tomorrow, alas.

Food Processing now processes. Mostly. Tiles may now hold buildings. Added building fields. Added some clusterfark to allow arbitrary building types to message properties over a network. Added a field to define food satiation for a unit (currently ceil 1/2 maxAP). Added a placeholder to define if two Empires are allied for food distribution or neutral and friendly... once I actually define how that works. Added AP-per-food field to racedefs and units.

And most importantly, I implemented the algorithm that was laid out in the game design (see last post's link). There were a few things I adjusted on the fly. Units now will only eat until they're almost full in the second crack at the granaries, and units will eat any food left in tiles only after it's stored in allied granaries.

The only actual thing missing from the Game Design was "which empires are used for the 'if allied' check when chaining granaries?". Units need food, and to avoid the tedium of transporting it I have granaries. They provide an area of supply, and if they're out of food, they'll automatically pull from a nearby granary (in range). Allies can share that food network. But what happens if there are 3 empires with granaries in a chain. A->B->C where A and B are empty, thus pulling from C. A is allied with B, B is allied with C, A is not allied with C. What should happen?

I decided that it was kosher, and that alliances are link isolated. C only cares that their friend B is getting food, and A doesn't care where B got them their food. Easily changed later, but it was interesting to see how that sort of disconnect between design and implementation comes about.

Telastyn

Telastyn

 

Mmm... forbidden donut.

After clearing up a bug with my test immortal monkey unit, I added the Food Processing Phase to Moe turn processing. Not much processing is being done yet alas; it's being blocked mostly by the lack of buildings and thus granaries as well as the lack of happiness (and thus what defines 'hungry' rather than sated but not full).

Still, it's a roadblock that now has a placeholder to allow me to placeholder other things (Trade Phase and Victory Conditions are left in the turn processing).

Telastyn

Telastyn

 

Insights of old code.

I have my evenings to myself for the rest of the week and a mostly empty weekend. I'm very near the end of the shoot yourself in the head game and I have a large bag of red swedish fish. Based on this, I started back into Moe with the hope of getting some headway in the nice lull time.


Working with your own code after a fairly lengthy break is always interesting. It provides a good insight into just how good/crappy you actually are. It's also amusing to re-read notes in the source or logs you leave yourself.

I unfortunately am not a good programmer. I've only been doing it for a decade, aced every course I've ever taken, and even make my living by it, but make no mistake; no good. I'm not quite sure about a bit of it.

Part is of course lacking the formal training that most good programmers have. Some have the instinctual ability to know the complexity of operations, data structures and the such. I don't particularly care. If it's associative, it gets a Dictionary/HashMap/map if not a List/List/vector. Most of my stuff tends to be slow; a bit verbose in what it does.

Part is leftover from my slow start with C++. I'm pretty comfortable with C# now, can do maintenance in Java, perl, and am still pretty good in C++ and plain C. But those are all in their nice little world (except perl of course), so some of the tricks and practices of language breadth just isn't there. I've dabbled with Scheme and know enough about them to get a good idea why Functional languages are seen as elegant. No practical experience though, and certainly nothing in anything more exotic that mainstream functional.

Part is my stubbornness. Though I'm not sure it's stubbornness... Given a problem, I can concoct a solution so there's not much drive to explore existing/better/common solutions. Decidedly the most detrimental of my flaws I think. Moe for example has 3 external libs: .NET, mdx, and fmod. Networking from scratch, ui from scratch, mapgen from scratch, input handling from scratch, logging from scratch, parsing from scratch, and on and on. It's dumb, it's inefficient, but I never seem not to do it. Every time I try to look into external libs, I never what to look for or have the patience to investigate if the lib will actually do what I want to do (let alone the patience to learn it well enough to integrate it into the code).


It's funny, because I'm a fairly excellent code monkey. Give me a problem and I'll belt something out pretty quick that is bug free, well designed, consistent, extensible. And I think this is part of my problem too. The only time I tend to 'experiment' or look into new stuff is when the current processes aren't working. Right now the toolbox is pretty effective from my perspective. Perspective being the key word. This only rather works with little stuff.

Give me big stuff/problems and it breaks down a little. Not that the big stuff doesn't get designed and implemented well. It just is super slow, and relatively unpolished compared to stuff I've seen others knock together. Most of it is 3rd party lib use, but I think there's more to it than that. So there's something there in my processes that suck compared to others' that I need to figure out or work at.


Just as soon as I get moe playable. Another one of my faults; being one of those many forum-goers who's never actually gotten a game done. Written, debugged, built, packaged, and released. I've never lost sleep over the problem. It's a hobby and a learning experience. I was amused by what I wanted, learned what I needed and moved along. Still, it is likely a symptom of other things and I could use the experience with some of the stuff late in the game. And some part of my delirium thinks that picking up a new language or project will be easier once this is off my shelf (hah!).


Oh, I diverge too much. Moe's code is actually quite good. There's a number of places that I can look back and am surprised by the elegance of bits of it. Most all of the utility classes are easily utilized. Things are consistent, mostly well named, pretty autonomous... Something I suppose to smile upon compared to the steaming piles which previously made up my 'best work'.

[edit: oh, and the game now has races for your little dudes which supply info about their birth rate and lifespan (and more to come)]

Telastyn

Telastyn

 

Penny Arcaded!

I just went through and picked up Armageddon Empires based on the little I read from today's Penny Arcade link. My hobby project is pretty much exactly the sort of market this game is in (hex based strategy on the PC by an indie sort of developer and priced accordingly) which means:

1. I'm interested in those sort of games.
2. It's useful to investigate to see what they did, what worked/what didn't.
3. I want to support them financially, since I would want the same once my game ever gets done.

Now to give it a test drive. Psuedo review will likely be forthcoming!

[edit: psuedo review!

The game really, really, really needs some sort of auto-resolve, but seems otherwise neat, and quite akin to the old Chaos Overlords game]

Telastyn

Telastyn

 

Parser Debugging.

Debugging parsers sucks nards.

I redid the PrimaryExpression parser for C#3.0 because my parsing framework doesn't handle left recursion. I then spent the rest of the day looking into why it didn't work. Turns out I forgot to put .Optional after the parser declaration for the postfix(++/--) operators. Not many expressions have the suddenly required ++ or -- to work...

Oopsie.


Anyways, the parser passes its current test: parse the source code for the test itself. 2300 lines of parse tree for ~20 lines of code... Maybe need to cut the verbosity down on that. So far this has been a pretty interesting exercise, and a little educational, but I've the nagging feeling that I could be doing something better with my time. I suspect I still re-implement things far far too much and such inclinations are what keep me from being even mediocre.

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!