0

So I'm making a "toy language" and also using a "toy compiler" to execute the code.

Basically, I am designing everything in C# and simply said how it works is by just making tokens from a source file, loop through them and design instructions using a list of C# Action.

I have tried "parsing/compiling" everything backwards and then reverse the Action-list when it's time to execute, which is super dumb considering how the problem is structured.

Here is the part of the source code from the "toy language"

printl("Hello, What is your name?")
string Name = inline()

printl("Oh, hello there " + Name)

And how my C# "toy compiler" goes through this is by adding actions so

printl("Hello, what is your name?") having the string inside the function as a token with a value gives the following parsing code:

Actions.Add(new Action(() => Console.WriteLine(CurrentTok.Value)));

Though having multiple values as in the last part of the code it simply takes an empty object and adds all the values in a loop by converting the values to string until the current token becomes a ')' RightParen Token. Resulting in a object with all the values that gets printed with the ToString() function.

And for the one where I have the inline() function gives the following also keeping in mind that I have a Dictionary of type <string, object> to store all the variables in.

Actions.Add(new Action(() => Variables[Var_name] = Console.ReadLine()));

Now the problem comes when parsing the last line where it's supposed to write out that value, since it's already been "compiled" and the variable having no value. After the inline() command has been executed. The variable doesn't update it's value, since it's in a list.

Here is a simplified version of the source from the "compiler" code, for trying to explain the problem better, Note. Current = Tokens[Index]

While(Index < Tokens.Count - 1) 
{ // Index a simple int
    if(Ignore.Contains(CurrentTok.Type)) // Type = Type of Token
        Index++ // if it's a  { or a }.. and so on
    if(CurrentTok.Type == TokenType.String) // TokenType = enum
    { 
        if(Current.Value == "inline()") 
        {
            Variables[Current.Symbol] = " "; // so it's not undefined 
            Actions.Add(new Action(() => Variables[Current.Symbol] = Console.ReadLine()
            )); // Current.Symbol being the variable name
        } else {
            Variables[Current.Symbol] = Current.Value;
        }
    }
    if(Current.Type == TokenType.Function) {
        if(Current.Symbol == "printl") {
            Index++;
            if(Current.Type == TokenType.LParen) { // '('
                Index++;
                object ToPrint = " "; // init an object
                While(Current.Type != TokenType.RParen) { // ')'
                    if(Current.Type == TokenType.Plus)
                        Index++;
                    if(Current.Type == TokenType.PrintString) {
                        // PrintString being a string inside a function
                        // that has not been declared as an variable.
                        object ToAdd = Current.Value;
                        ToPrint += Convert.ToString(ToAdd);
                    }
                    if(Current.Type == TokenType.String) {
                        object ToAdd = GetVar(Current.Symbol); 
                       //GetVar = object that returns the value if the dictionary contains it
                        ToPrint += Convert.ToString(ToAdd);
                    }
                    Index++;
                }
                Actions.Add(new Action(() => Console.WriteLine(ToPrint)));
            } else {
                // errors...
            }
        }
    }
    index++;
}

from the source code that i listed above it works normally, it prints the text Hello, What is your name and opens the inputstream with the readline. But returns Oh, heloo there without the name.

K. Hansson
  • 21
  • 9
  • I don't understand why you can't look-up again `Variables[Var_name]`. Do you want the compiler to check that the variable exists? – hugo Aug 08 '19 at 22:00
  • @hugo can you elaborate, how do you mean look up? Everything gets stored in a list of Action, then later after the parsing is done, it executes every action. Giving the problem that i stated since the variable is given in the Action without a value.and yes i have a function that looks up if the variable exists, returning "undefined" if it's not available – K. Hansson Aug 08 '19 at 22:02
  • Make another list of tokens ´List´ (or a HashSet, whatevs) that you update at compile-time. At any step during compilation, you use it to know if a declaration matches this token. – hugo Aug 08 '19 at 22:11
  • @hugo now again i don't see how that would work out? since everything is already in the list, how do i go to that specific index after the action before has been executed to update it? is there someway to add an event to a value assignment? – K. Hansson Aug 08 '19 at 22:16
  • Ok it’s hard to explain in the comments, I tried in an answer – hugo Aug 08 '19 at 22:28
  • 1
    Too much information is missing from your question… You should provide code that actually show the problem. – Phil1970 Aug 08 '19 at 22:51

2 Answers2

0

Keep track of your vars at compile-time:

HashSet<string> scopeVars = new HashSet<string>()

When you parse an assignation, do:

Actions.Add(new Action(() => Variables[Var_name] = Console.ReadLine()));
scopeVars.Add(Var_name);

And when you encounter that token (which is recognized as being a var) in an expression, do:

if (!scopeVars.Contains(Var_name)) {
    // Compile error!
} else {
    Actions.Add(...);
}

You’ll probably want a `Dictionary<,>ˋ instead to store the type of the variable or whatnot, but I didn’t make any assumption on your language.


In response to your edit:

You can't do that at compile-time, since your var only gets a value at runtime:

object ToAdd = GetVar(Current.Symbol); 
//GetVar = object that returns the value if the dictionary contains it
ToPrint += Convert.ToString(ToAdd);

Here is a simple solution -- first you'll need to initialize this string list when you start parsing a "printl":

var VarsToPrint = new List<string>();

Add a placeholder for the variable, instead of computing its value (you can't do that during compilation!). Replace the code I first quoted with:

var toAdd = "{" + VarsToPrint.Count + "}"; // will add {0} for the 1st var, {1} the next time, and so on
VarsToPrint.Add(Current.Symbol);
ToPrint += toAdd;

So you would make the actual variable evaluation inside your action:

Actions.Add(new Action(() => Console.WriteLine(ReplaceVars(ToPrint, VarsToPrint))));

string ReplaceVars(string s, IEnumerable<string> vars) {
    var values = VarsToPrint.Select(varname => Variables[varname]); // fetch the values associated with the variable names (uses System.Linq)
    return string.Format(s, values.ToArray()); // replace {0}, {1}, {2}... with the values fetched
}

You will quickly see the limitations of this approach, but hopefully it will help you understand the problem and build something more powerful.

hugo
  • 3,067
  • 2
  • 12
  • 22
  • This seems like a good answer which can be a good foundation for a solution.Though after implementing it, it still doesn't do what i thought it would do. Since still, the variable doesn't get added to the `scopeVars` until the action is executed, which means that the other parsing for adding the value has already gone through resulting, still, in a null/no-value state for the variable that's supposed to get printed... – K. Hansson Aug 08 '19 at 22:58
  • Since you updated your answer, I can see what's happening. See my edit. – hugo Aug 09 '19 at 08:16
0

The problem is that you construct ToPrint value when you parse the script and when you call GetVar(Current.Symbol), value is not there yet as it will be initialized during execution. The simplest option will be just to defer evaluation of ToPrint to execution.

There are many ways to do that, for example convert it to Func:

Func<String> ToPrint = ()=> " "; // // this is func, evaluation is deferred to invocation
  While(Current.Type != TokenType.RParen) { // ')'
     if(Current.Type == TokenType.Plus)
        Index++;
     if(Current.Type == TokenType.PrintString) {
        var ToAdd = Current.Value;
        var currentValue = ToPrint; // you cannot use ToPrint in the clausure :(
        ToPrint = () => currentValue() + Convert.ToString(ToAdd);
     }
     if(Current.Type == TokenType.String) {
        var ToAddSymbol = Current.Symbol; 
        //GetVar = object that returns the value if the dictionary contains it
        var currentValue = ToPrint 
        ToPrint = () => currentValue() + Convert.ToString(GetVar(ToAddSymbol)); // GetVar will be called during execution
     }
     Index++;
  }
Actions.Add(new Action(() => Console.WriteLine(ToPrint())));

This solution is not good neither from stack usage perspective (what if there will be many tokens in the string?) nor from memory perspective (how many closures is here?). You may play around with the implementation, but the main idea is to defer GetVar call to the execution time.

Writing a parser is not a new task, there is a lot of frameworks to do so, you may read this thread to get an extensive overview. Your parser solves very custom scenarios. It might be painful and risky to add new features, for example, think what it will cost to add nested expressions? I don't blame your implementation, but there is so much already written, feel free to learn it and use :)

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
fenixil
  • 2,106
  • 7
  • 13