C# and an IF Platform

An ongoing side project of mine is to develop a C# based story authoring system. I’ve been using ChatGPT and more recently Claude Opus to cowrite the code.

C# has an interesting capability that aligns with core IF requirements. I’m talking about its ability to return multiple values from a function. So we might do something like this:

private (string text, Action action) Parse(string command)

In this case “action” is another function we can use to continue executing our parsed command.

Veddy intresting.

2 Likes

Tuples are indeed situationally very useful! I think they’re supported out of the box in TADS 3, and my Data Structures extension added them to Inform 7 (6M62).

2 Likes

An interesting idea that might make some things a bit neater, but I usually invent an “object” whenever i have more than one thing.

When you need a third thing here to be returned, presumably you have to change your callers instead of just adding that thing to the object.

eg

struct ParseResult
{
    string      text;
    Action      action;
    Thing       thing;
};

bool Parse(string command, ParseResult&);
1 Like

Several modern languages support functions with multiple return values, such as Python, Lua and Go. But as pointed out above, often it’s better to define a type that makes it clear what exactly you’re returning: what are all those values supposed to represent? (Robustness is a bonus, too.)

That said, I had no idea C# of all things had this particular feature. Good catch! Wonder when it was added and why.

1 Like

Using objects as parameters and return structures has always been available and it is likely the more efficient solution, but tuples are interesting.

If I know the parser (or some other function) is unlikely to change, tuples might offer better readability.

It’s definitely a narrow choice.

I’d assume this is another F# feature that’s migrated to C#.

Tuples have been in C# for about 7 or 8 years, I think. They were kind of half-baked in the beginning, but have gotten better. Rust has them as well.

They’re useful to cut down the number of boilerplate “objects” you need for small things. For instance, when returning both a value and an index when enumerating a collection.

2 Likes

I can’t think of any popular language that doesn’t support multiple return values, except for C and Java.

JavaScript, Python, C++17, PHP, Go, Rust, Kotlin, Ruby, Lua, Dart, and Swift all support it.

Some languages call this feature “destructuring,” where you’re technically returning just one object or array, and then the caller converts the return value into multiple local variables in a single line.

I think the main thing a parser IF platform needs is excellent syntax for large amounts of declarative data, which is where Inform 7 really shines.

3 Likes

You mean like Structure in C? Return a struct pointer and let the calling function deal with it.

You could always “let the caller deal with it,” but destructuring is a little nicer.

In JavaScript, for example, before destructuring, you could do this:

function x() {
  return {foo: 1, bar: 2};
}

var r = x();
var foo = r.foo;
var bar = r.bar;
console.log(foo);
console.log(bar);

That’s “letting the caller deal with it.”

But with destructuring, you can do this:

function x() {
  return {foo: 1, bar: 2};
}

var {foo, bar} = x();
console.log(foo);
console.log(bar);
1 Like

I want avoid comparisons to existing platforms and their clear advantages and disadvantages.

My interests are in building something that has modern software development concepts like separation of concerns, 3GL extensibility, and very specific features like the world model contained in an in-memory graph data structure and output structured as a contextual service similar to fyrevm’s channel IO. Allowing for custom user interfaces is a given.

The parser and grammar aspects are likely to mimic Inform 6 and TADS.

Fluid C# syntax can provide the same declarative syntax that Inform 7 has though it’s a challenge to bring all of it together.

My brain has struggled with “what is core functionality?”, “what is standard IF functionality?”, and “how would authors build stories?”.

It turns out, this is the hardest aspect of attempting what I’m doing.

2 Likes

Those are excellent questions, and just like in a general purpose programming language, it can be hard to draw a line. So you’re going for an embedded DSL?

1 Like

Fluid syntax in the very fragile grammar code I have now:

_grammar = new Grammar();
_world = world;

Player = new Player("player", "Player", "You are a scruffy adventurer.");
_world.AddNode(Player.Id, Player);

// Standard actions
var standardTake = _grammar.Verb("take", Take).Noun.End;
var metaExamine = _grammar.Verb("examine", Examine).Noun.End;
var standardGoInNoun = _grammar.Verb("go", Go).In.Noun.End;
var standardGoNoun = _grammar.Verb("go", Go).On.Noun.End;

// Meta actions
_grammar.MetaVerb("score", Score);
_grammar.MetaVerb("restore", Restore);
_grammar.MetaVerb("restart", Restart);

// Override the "go" action
var goInNoun = new List<Token>
{
new Token(TokenType.Verb, "go", Go, ActionType.Standard),
new Token(TokenType.Preposition, "in"),
new Token(TokenType.Noun)
};

_grammar.OverrideActionDelegate("go", goInNoun, CustomGo);
_world = world;

1 Like

You got that right. It is indeed the hardest part. Many people think that Coding is the hardest part, but actually Design is the hardest part.

4 Likes

In C that’d be equivalent to

function x(r) {
  r.foo = 1; r.bar = 2;
  return (r); 
}

r = x(r); // optional return for a pointer :)
console.log(r.foo);
console.log(r.bar);

There’s a necessity of implicit understanding of how the structured variable fit in the context of overall program, but that’s what programmer’s journal is for.

Personally, I don’t even use struct, preferring simple array (tabular) data structure. But ,well, to each his own!

1 Like

Never truer words spoken.

2 Likes

“what is core functionality?”, “what is standard IF functionality?”, and “how would authors build stories?”.

If you’re going to invent a DSL, these questions partition into things written in the DSL and things that are part of the runtime.

So;

  1. core functionality = runtime (C# or whatever).
  2. standard IF functionality = DSL
  3. how authors build = DSL

However;

DSL in (2) winds up in some kind of user-level lib. This works out kind of neat, because although this is meant to be the “standard IF”, it is nevertheless editable for stories that want to change it. Because some always do!

Even if you’re not planning on introducing a DSL, this is an interesting way to think about how it might be partitioned.

1 Like

In my current design, there are structural components that provide data storage, event management, and the turn loop.

From there I have libraries for Grammar, Parser, and Standard IF.

I would equate those as a sort of DSL since I’m using Fluid design techniques which are not really needed in business applications outside of pipeline patterns.

The hierarchy is:

  • Data Store
  • Text Emission Service
  • Event Broker
  • Rules Engine
    ** Grammar
    ** Parser
    *** Standard IF
    **** Turn Loop

Story (as a class library)

This is my latest iteration. I think I’ve gone through this about five times now. I’ll probably iterate several more times before solidifying what I want to build.

Looks complex enough. Guess it can’t be helped. Nitpick: you probably mean a fluent interface, not fluid. I was mixing them up, too, and couldn’t figure out why a web search turned out entirely wrong results while working on the Jaiffa library.

You are correct - my bad.

Digging into this again…

I’ve decided (for now) to go down the path of not having declared objects for anything and that everything in the story (the world) will be a node and connected with edges. Nodes have their own properties. Nodes will automatically fire a NodeChanged event for any state change. These change events will get translated to IF constructs at higher layer.

This would be how story state change is handled. The author can identify nodes by name (location, story object, PC, NPC) and catch specific state changes. A node identified as a door would be a node connected (generally) to two location nodes. If the door has a property for Open/Closed and changes, the author could trap when that happens…

Core.Events.Catch("front door", "open", FrontDoorOpened);

private void FrontDoorOpened() {

// what happens when the front door is opened

}

This is being roughed out for now, but I love this path in my R&D. The idea that the world is a true bidirectional graph of IF constructs feels “right”.

The challenge will be to apply all IF properties and functionality to nodes with one abstraction layer. (the nodes and edges contain data, but don’t actually know anything about IF).