Code Monkey home page Code Monkey logo

Comments (9)

VinGarcia avatar VinGarcia commented on May 30, 2024

Hello @grazba, I am happy you liked it, thanks for using it and for all the feedbacks =].

I was thinking about how my statements are defined in my programming language and I found that I wanted to simplify it before I write about how I did it. That's why I was taking some time to answer =P.

After I am done adding a new statment will work much like the way we add new functions/operations/etc, just add an instance of a class to a std::map and everything will work.

I am making this improvement and I hope I can finish it today =].

from cparse.

VinGarcia avatar VinGarcia commented on May 30, 2024

@grazba, hello again!

I have written some examples on the examples page (that until now was mostly empty).
This should help you implement any type of statement you like.

The good news is that the code is not complicated.
The bad news is that it is quite big πŸ˜“ .

I am considering adding some built-in implementations of those classes into the library since they represent a lot of work for little extra value. πŸ€”

In any case, this is a task for the future, I hope these examples help you.
Also, since I just finished writing this page it will probably have some problems with the text and even on the code. I will test this code myself when I have some time. Until then feel free to mention any problems you find.

from cparse.

 avatar commented on May 30, 2024

Hi !!
thanks for the detailed example ! i will work on it and get beck to you if i detect any typos ^^ ! i agree maybe the statement mechanism should be a part of cparse in the future =] !

cheers πŸ‘

from cparse.

 avatar commented on May 30, 2024

@VinGarcia
Hello again , i'm struggling to implement an IF as a function , that takes a condition and value if true and another value if false.

i added if as a keyword and implemented a function that parses the expression extracts the tokens then calls a personal function that does the operation on data (reading from disk checking the condition and changing the values accordingly) and returns a packToken in my case a tokenMap of one of my types and then call handleToken with this returned object. i don't what happens exactly after handleToken but it goes to string on number operation function, i must be missing a point.

If you can too , help me with internal function to handle operator parsing, i'm doing it manually and i think maybe there is a better way to do it.

void ifParser(const char* expr, const char** rest, rpnBuilder* data) 
	{
		while (isspace(*expr)) ++expr;

		if (*expr != '(')
			throw syntax_error("Expected '(' after `if` statement!");
		else
			expr++;

		std::string name = rpnBuilder::parseVar(expr);

		std::cout << name << std::endl;

		auto& globalMap = TokenMap::default_global();

		auto it = globalMap.find(name);

		std::string op;
		if (it)
		{
			auto left = calculator::calculate(expr, globalMap, "<>",&expr);

			std::cout << *expr << " " << *(expr + 1) << std::endl;
			if (*(expr + 1) == '=')
			{
				if (*expr == '<')
					op = "<=";
				else if (*expr == '>')
					op = ">=";

				expr++; expr++;

				auto right = calculator::calculate(expr, globalMap, ",", &expr); expr++;

				auto trueValue = calculator::calculate(expr, globalMap, ",", &expr); expr++;

				auto falseValue = calculator::calculate(expr, globalMap, ")", &expr); 

				auto test = Wells::FPS_LOG.getChild();

				test["logSize"] = 666;
				test["logName"] = "Test Log";

				if (*expr == ')')
				{
					data->handle_token(test.clone());//mapOnNumIf(left, right, op, trueValue.asDouble(), falseValue.asDouble())->clone());
					expr++;
				}
				else
					throw "Expecting closing ) !";
etc ...

from cparse.

VinGarcia avatar VinGarcia commented on May 30, 2024

Hello @grazba, I think there is a trick you are missing. This line:

auto falseValue = calculator::calculate(expr, globalMap, ")", &expr);

Will parse expression until it finds a ")" that is not preceded by a "(". In other words, it will parse the entire boolean expression in a single step.

The trick here is that we don't need to get the name of the variables nor check each operator separately, all we need is the result of this expression so we just let the parser deal with it and return when it finds the ")" character that ends the condition.

Another thing that is important is that all this parsing happens at compilation time, thus, anything we do will be kept inside the RPN to be evaluated when the time comes. This means that using calculator::calculate() might not be what you meant since evaluating it at this moment would ignore the variables that existed at execution time and give always the same result.

To fix this you should create a compiled expression and save to be evaluated at execution time:

const char* endOfStatementDelimiters = ";\n";

while (isspace(*expr)) ++expr;

if (*expr != '(')
	throw syntax_error("Expected '(' after `if` statement!");
else
	expr++;

// parse the boolean expression until it finds the ")" character:
calculator boolExpression(expr, globalMap, ")", &expr);
++expr;
// Parse a single statement imdeately after the "if (...)":
calculator trueCase(expr, globalMap, endOfStatementDelimiters, &expr);
++expr;

calculator falseCase;

// Peek the first word after the first statement:
std::string name = rpnBuilder::parseVar(expr);
// If it is an `else`:
if (name == "else") {
  // Now parse the word else and modify expr to point to the first character after the word:
  rpnBuilder(expr, &expr);
  // Parse a single statement right after that for the false case:
  falseCase.compile(expr, globalMap, endOfStatementDelimiters, &expr);
  ++expr;
}

I would like to tel you that this is all but there is still a problem to solve.
This function you are using is expecting a rpnBuilder to be received as an argument, thus, you are planing on registering it as a reserved keyWord for the parser.

This is not wrong and it will allow for example for if expressions to be evaluated in any place in your code, e.g.:

myFunc(if (cond < 10) 10; else 20;);

However, for my own programming language, I considered this an advanced approach and I created the behavior of the if statement as well as the for, while, etc without using any internal features of the cparse library. I just used it to parse and compile expressions contained in statements and then I saved an array of statements as being my code. To run the code I execute each statement at a time and each of those executes its internal compiled calculators according to the expected behavior (e.g. the if statement class will only execute one of the 2 compiled expressions either the trueCase or the falseCase).

The main reason I say that using the reserved keyWord approach is an "advanced" way of doing it, is because your parser will be working inside the calculator library. It means it can only save its sate by adding things to the RPN. This means that if you continue with this approach you will have to create some new types of tokens to store the compiled values for the 3 calculators we used on the example above and a new operator (possibly with an invalid operator's name such as if_stmt so normal users can't write it by accident) that will keep in the RPN the information that some code must execute during execution time.

So for example the resulting rpn for the example above with myFunc(...) would look something like this:

myFunc, IfExprToken, IfBodyToken, if_stmt, ()

Note: myFunc is token of type variable, ifExprToken and ifBodyToken you will have to create,
if_stmt is an operator that will receive the 2 tokens as left and right arguments so you can do
the if logic inside it and the operator () is the built-in operator for calling functions,
it will just pass the result of the if_stmt operator as an argument to myFunc.

So it would operate between the 2 new tokens before sending the result to be used as arguments of myFunc function.

Note that this approach can also be used with a unary operator,
since all you want is to execute a piece of code at execution time,
it does not matter if you separate the structures in 2 or just keep a single token with all compiled
calculators and make the if_stmt as a unary statement.

So this is not simple, but I hope this helped a little. If you want to keep the approach of using the reserved keyWord parser, tell me and I can give you an example of how to do this. If you choose the other approach I can give examples as well.

Also, since I have to leave now I will not have time to re-read this text, I hope it is clear as it is.

from cparse.

 avatar commented on May 30, 2024

Actually i don't have a choice here, i can't just parse the boolean expression, because it's not , what i'm trying to do is the following (it is working but it's not good looking and i didn't handle logical AND and OR.

My data types as i said before are big arrays of data, that i can't load on RAM directly. The IF(cond,value,Fvalue) takes a condition and checks it for every item of my data if it checks out write the Tvalue , if not write Fvalue. What i need to be able to do , is parse the condition and extract the operators and the operands, i'm pretty sure there is a mechanism that let's me do that and i would love to have help on this side of things.

Regarding my use of the static calculate function, i don't need to keep track of what happened before, the result of my if is a value of one of my types not a boolean. This is not an if statement, the example you have me regarding that was just enough. there is no statements to be executed if true or false it's like a lambda function that we apply to every element of a vector.

We can think of it as a function that takes a logical expression as a parameter that i need to parse and divide to operands and operator, i'm pretty sure that's the RPN's work, but i can't figure out how to use it.

Thanks in advance i hope it's more clear now !

from cparse.

VinGarcia avatar VinGarcia commented on May 30, 2024

Yes, I think I understand it better now πŸ€”,

One way to put hands on the RPN is to use a function that is normally used internally:

static TokenQueue_t toRPN(const char* expr, TokenMap vars,
                          const char* delim = 0, const char** rest = 0,
                          Config_t config = Default());

It accepts the same 4 arguments as the compile/calculate, you will probably not have to use the last one.

The RPN is kept on a TokenQueue_t (a.k.a. std::queue<TokenBase*>). I think I should probably replace it for a std::vector for simplicity of usage, but for now it is what we have.

Iterating over such a queue has 2 caveats:

  1. It does not have a default iterator, thus, you have to call 2 functions to get the next item: .front() to return the value of the current item and then .pop() to remove the current item from the top of the list.
  2. Each TokenBase* must be deleted after usage or you'll get a memory leak, a suggestion for simplifying your usage is to wrap each TokenBase* on a packToken as soon as you get it, so the memory management is dealt by the packToken.

An example usage:

const char* expr = "a < b, Tvalue, Fvalue";
TokenQueue_t rpn = calculator::toRPN(expr, TokenMap(), ",", &expr);
while (rpn.size()) {
  std::cout << packToken(resolve_reference(rpn.front())).str() << std::endl;
  rpn.pop();
}

Note: The function resolve_reference() will convert tokens of the type REF into actual values.

This will allow you to interact with all the items of the expression.


But having said that, I still think that the complexity of these expressions will limit what you can put on these map operations.

For example, if you read the first token and it is a variable name it is still risky to assume you have the left operand because sometimes the expression might be something like this:

  • if (someMap['some_attr'] <= 10, ..., ...)

And when using the RPN this type of expression looks weird and might be hard to parse correctly, i.e.:

someMap, 'some_attr', [], 10, <=

I am not entirely sure I completely understood the complexity of the problem, but if you have to extract the operands and operator and then use them, it should be easier and less risky to compile the boolean expression into a calculator, and use it as you would use a function afterwards, i.e. call its .eval(TokenMap()) function passing as arguments a TokenMap with the variables it should access.

Again, I might be underestimating the problem here. If I am, sorry for that =P.


Back to your original question about what the data->handleToken(TokenBase*) does: It is responsible for inserting the given token on the top of the RPN, it also sets some internal variables so we know the last token was a token and not an operator, nor an unary operator, it should not cause any exceptions, the code for this function follows below:

void rpnBuilder::handle_token(TokenBase* token) {
  rpn.push(token);
  lastTokenWasOp = false;
  lastTokenWasUnary = false;
}

I hope this helps but I know how complex this type of thing is, so I might have lost the point again =P

from cparse.

 avatar commented on May 30, 2024

I just tested the toRPN function , and it can be useful because i think it's still better than working on the string manually character by character. I suppose i can check the type of the elements returned by this function , (check if it is a number or an operator for example ?).

i'm thinking about another solution, because i need to store the conditions . In reality the calls for the if will always be like this :
if(FN("name") > value AND/OR FN("name) < value , Tvalue,Fvalue)
where FN("name") is a function that handle data loading and will return a TokenMap (compound type) of a certain type (the data types i handle). it would help a lot to check the type of the token specially when it's not a MAP , in my code i use try catch statement to handle packTokens that are not maps (.asMap()) and i don't think it's a nice way of doing things.

I can implement the logical operators for my types but i need to think of a way of representing the data that keeps everything logical like creating a new compound type called CONDITION where i store the info about the operands and operator, and store the conditions.

My way of doing things right now can handle only one condition, it would be nice to be able to have as many as possible with logical AND/OR.

So to recap, the goal is to store conditions/Logical expressions in a meaningful way because i have to evaluate them my self.

Thanks man , you have been very helpful with information and don't worry about the point all information you're giving is valuable , just one important point my conditions don't return a bool but actually a big array of bools and i can't just create such an array it won't be memory efficient, so i have to store my condition and evaluate it when i read from disk and right the values. I hope it's clear now ^^

from cparse.

VinGarcia avatar VinGarcia commented on May 30, 2024

Hello @grazba, I am sorry for not answering your last comment, I probably missed the e-mail that should notify me of your new message and life happened =P.

I assume you have probably figured out a solution to this problem by now after all this time but I think I can give you at least one tip that might be helpful, when you mentioned the way you are testing for maps:

"... in my code i use try catch statement to handle packTokens that are not maps (.asMap()) and i don't think it's a nice way of doing things.

The correct way to perform this test is to use the token ->type and compare it with the type you want, e.g.:

  packToken token = TokenMap();

  if (token->type == MAP) {
    std::cout << "It is a map!" << std::endl;
  } else {
    std::cout << "It is not a map!" << std::endl;
  }

Also, maybe what you were looking for was a function declaration, i.e. a way to store code into a variable so you can call it later. Maybe the class FuncDeclaration on my programming language can be useful as an example:

If there still something about this issue you want to discuss feel free to reopen it, I am sorry I missed this issue for so long, I am not having much time to work on this project lately =[.

from cparse.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.