Add expression evaluation
This commit is contained in:
		@@ -14,6 +14,11 @@ void ErrorHandler::error(Token token, const std::string &message) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ErrorHandler::runtime_error(const RuntimeException &exception) {
 | 
				
			||||||
 | 
					  std::cout << "RuntimeException [" << exception.token.line << "]: " << exception.what() << '\n';
 | 
				
			||||||
 | 
					  had_runtime_error = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ErrorHandler::report(int line, const std::string &where, const std::string &message) {
 | 
					void ErrorHandler::report(int line, const std::string &where, const std::string &message) {
 | 
				
			||||||
  std::cout << "[line " << line << "] Error" << where << ": " << message << '\n';
 | 
					  std::cout << "[line " << line << "] Error" << where << ": " << message << '\n';
 | 
				
			||||||
  had_error = true;
 | 
					  had_error = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,15 +2,29 @@
 | 
				
			|||||||
#define ERROR_HANDLER_HH
 | 
					#define ERROR_HANDLER_HH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "tokenizer.hh"
 | 
					#include "tokenizer.hh"
 | 
				
			||||||
 | 
					#include <exception>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct RuntimeException : public std::exception {
 | 
				
			||||||
 | 
					  RuntimeException(Token token, const std::string& message) : token{token}, message{message} {}
 | 
				
			||||||
 | 
					  const char* what() const noexcept override {
 | 
				
			||||||
 | 
					    return message.c_str();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Token token;
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  std::string message;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct ErrorHandler {
 | 
					struct ErrorHandler {
 | 
				
			||||||
  ErrorHandler() : had_error{false} {};
 | 
					  ErrorHandler() : had_error{false} {};
 | 
				
			||||||
  void error(int line, const std::string &message);
 | 
					  void error(int line, const std::string &message);
 | 
				
			||||||
  void error(Token token, const std::string &message);
 | 
					  void error(Token token, const std::string &message);
 | 
				
			||||||
 | 
					  void runtime_error(const RuntimeException &exception);
 | 
				
			||||||
  void report(int line, const std::string &where, const std::string &message);
 | 
					  void report(int line, const std::string &where, const std::string &message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool had_error;
 | 
					  bool had_error;
 | 
				
			||||||
 | 
					  bool had_runtime_error;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,9 @@ void run_file(const char *path) {
 | 
				
			|||||||
    if (error_handler.had_error) {
 | 
					    if (error_handler.had_error) {
 | 
				
			||||||
      exit(EX_DATAERR);
 | 
					      exit(EX_DATAERR);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (error_handler.had_runtime_error) {
 | 
				
			||||||
 | 
					      exit(EX_SOFTWARE);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,6 +48,7 @@ void run_prompt() {
 | 
				
			|||||||
    run(line);
 | 
					    run(line);
 | 
				
			||||||
    std::cout << PROMPT;
 | 
					    std::cout << PROMPT;
 | 
				
			||||||
    error_handler.had_error = false;
 | 
					    error_handler.had_error = false;
 | 
				
			||||||
 | 
					    error_handler.had_runtime_error = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,8 +59,13 @@ void run(const std::string &code) {
 | 
				
			|||||||
  Parser parser{tokens};
 | 
					  Parser parser{tokens};
 | 
				
			||||||
  std::optional<Expr> expression = parser.parse();
 | 
					  std::optional<Expr> expression = parser.parse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static AstInterpreter interpreter{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (error_handler.had_error) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (expression) {
 | 
					  if (expression) {
 | 
				
			||||||
    AstPrinter printer{};
 | 
					    interpreter.interpret(*expression);
 | 
				
			||||||
    std::cout << printer.print(*expression) << '\n';
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,5 +4,6 @@
 | 
				
			|||||||
#include "parser/expr.cc"
 | 
					#include "parser/expr.cc"
 | 
				
			||||||
#include "parser/printer.cc"
 | 
					#include "parser/printer.cc"
 | 
				
			||||||
#include "parser/parser.cc"
 | 
					#include "parser/parser.cc"
 | 
				
			||||||
 | 
					#include "parser/interpreter.cc"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,5 +4,6 @@
 | 
				
			|||||||
#include "parser/expr.hh"
 | 
					#include "parser/expr.hh"
 | 
				
			||||||
#include "parser/printer.hh"
 | 
					#include "parser/printer.hh"
 | 
				
			||||||
#include "parser/parser.hh"
 | 
					#include "parser/parser.hh"
 | 
				
			||||||
 | 
					#include "parser/interpreter.hh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										168
									
								
								cclox_src/parser/interpreter.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								cclox_src/parser/interpreter.cc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,168 @@
 | 
				
			|||||||
 | 
					#include "interpreter.hh"
 | 
				
			||||||
 | 
					#include "expr.hh"
 | 
				
			||||||
 | 
					#include "../error_handler.hh"
 | 
				
			||||||
 | 
					#include "../tokenizer.hh"
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <cassert>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern ErrorHandler error_handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AstInterpreter::interpret(const Expr &expression) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    Object value = evaluate(expression);
 | 
				
			||||||
 | 
					    std::cout << value.to_string() << '\n';
 | 
				
			||||||
 | 
					  } catch (RuntimeException e) {
 | 
				
			||||||
 | 
					    error_handler.runtime_error(e);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Object AstInterpreter::evaluate(const Expr &expr) {
 | 
				
			||||||
 | 
					  switch (expr.type) {
 | 
				
			||||||
 | 
					    case ExprType::BINARY:
 | 
				
			||||||
 | 
					      return evaluate_binary_expression(expr);
 | 
				
			||||||
 | 
					    case ExprType::GROUPING:
 | 
				
			||||||
 | 
					      return evaluate_grouping_expression(expr);
 | 
				
			||||||
 | 
					    case ExprType::LITERAL:
 | 
				
			||||||
 | 
					      return evaluate_literal_expression(expr);
 | 
				
			||||||
 | 
					    case ExprType::UNARY:
 | 
				
			||||||
 | 
					      return evaluate_unary_expression(expr);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Object AstInterpreter::evaluate_binary_expression(const Expr &expr) {
 | 
				
			||||||
 | 
					  std::shared_ptr<_Binary> ptr = std::get<std::shared_ptr<_Binary>>(expr.value);
 | 
				
			||||||
 | 
					  assert(ptr != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Object left  = evaluate(ptr->left);
 | 
				
			||||||
 | 
					  Object right = evaluate(ptr->right);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (left.type == ObjectType::NUMBER && right.type == ObjectType::NUMBER) {
 | 
				
			||||||
 | 
					    double left_val = std::get<double>(left.value);
 | 
				
			||||||
 | 
					    double right_val = std::get<double>(right.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (ptr->op.type) {
 | 
				
			||||||
 | 
					      case TokenType::PLUS:  return left_val + right_val;
 | 
				
			||||||
 | 
					      case TokenType::MINUS: return left_val - right_val;
 | 
				
			||||||
 | 
					      case TokenType::STAR:  return left_val * right_val;
 | 
				
			||||||
 | 
					      case TokenType::SLASH: {
 | 
				
			||||||
 | 
					        if (right_val == 0) {
 | 
				
			||||||
 | 
					          throw RuntimeException{ptr->op, "Division by zero."};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return left_val / right_val;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case TokenType::GREATER:       return left_val > right_val;
 | 
				
			||||||
 | 
					      case TokenType::GREATER_EQUAL: return left_val >= right_val;
 | 
				
			||||||
 | 
					      case TokenType::LESS:          return left_val < right_val;
 | 
				
			||||||
 | 
					      case TokenType::LESS_EQUAL:    return left_val <= right_val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case TokenType::BANG_EQUAL:  return left_val != right_val;
 | 
				
			||||||
 | 
					      case TokenType::EQUAL_EQUAL: return left_val == right_val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      default: throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for numbers."};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (left.type == ObjectType::STRING_LIT) {
 | 
				
			||||||
 | 
					    right = Object{StringObjectType::LITERAL, right.to_string()};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (right.type == ObjectType::STRING_LIT) {
 | 
				
			||||||
 | 
					    left = Object{StringObjectType::LITERAL, left.to_string()};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (left.type == ObjectType::STRING_LIT && right.type == ObjectType::STRING_LIT) {
 | 
				
			||||||
 | 
					    std::string left_val = std::get<std::string>(left.value);
 | 
				
			||||||
 | 
					    std::string right_val = std::get<std::string>(right.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (ptr->op.type) {
 | 
				
			||||||
 | 
					      case TokenType::PLUS: return Object{StringObjectType::LITERAL, left_val + right_val};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case TokenType::BANG_EQUAL:  return left_val != right_val;
 | 
				
			||||||
 | 
					      case TokenType::EQUAL_EQUAL: return left_val == right_val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      default: throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for strings."};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (left.type == ObjectType::BOOL && right.type == ObjectType::BOOL) {
 | 
				
			||||||
 | 
					    bool left_val = std::get<bool>(left.value);
 | 
				
			||||||
 | 
					    bool right_val = std::get<bool>(right.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (ptr->op.type) {
 | 
				
			||||||
 | 
					      case TokenType::BANG_EQUAL:  return left_val != right_val;
 | 
				
			||||||
 | 
					      case TokenType::EQUAL_EQUAL: return left_val == right_val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      default: throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for booleans."};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (left.type == ObjectType::NIL && right.type == ObjectType::NIL) {
 | 
				
			||||||
 | 
					    switch (ptr->op.type) {
 | 
				
			||||||
 | 
					      case TokenType::BANG_EQUAL:  return false;
 | 
				
			||||||
 | 
					      case TokenType::EQUAL_EQUAL: return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      default: throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for null."};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (left.type == ObjectType::NIL) {
 | 
				
			||||||
 | 
					    switch (ptr->op.type) {
 | 
				
			||||||
 | 
					      case TokenType::BANG_EQUAL:  return true;
 | 
				
			||||||
 | 
					      case TokenType::EQUAL_EQUAL: return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      default: break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  throw RuntimeException{ptr->op, "Operator " + ptr->op.lexeme + " is not supported for operands " + left.to_string() + " and " + right.to_string() + "."};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Object AstInterpreter::evaluate_grouping_expression(const Expr &expr) {
 | 
				
			||||||
 | 
					  std::shared_ptr<_Grouping> ptr = std::get<std::shared_ptr<_Grouping>>(expr.value);
 | 
				
			||||||
 | 
					  assert(ptr != nullptr);
 | 
				
			||||||
 | 
					  return evaluate(ptr->expr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Object AstInterpreter::evaluate_literal_expression(const Expr &expr) {
 | 
				
			||||||
 | 
					  std::shared_ptr<_Literal> ptr = std::get<std::shared_ptr<_Literal>>(expr.value);
 | 
				
			||||||
 | 
					  assert(ptr != nullptr);
 | 
				
			||||||
 | 
					  return ptr->value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Object AstInterpreter::evaluate_unary_expression(const Expr &expr) {
 | 
				
			||||||
 | 
					  std::shared_ptr<_Unary> ptr = std::get<std::shared_ptr<_Unary>>(expr.value);
 | 
				
			||||||
 | 
					  assert(ptr != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Object right = evaluate(ptr->right);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  switch (ptr->op.type) {
 | 
				
			||||||
 | 
					    case TokenType::MINUS: {
 | 
				
			||||||
 | 
					      switch (right.type) {
 | 
				
			||||||
 | 
					        case ObjectType::NUMBER: {
 | 
				
			||||||
 | 
					          double value = std::get<double>(right.value);
 | 
				
			||||||
 | 
					          return -value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        default: throw RuntimeException{ptr->op, "Operand to unary operator - must be a number."};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case TokenType::BANG:
 | 
				
			||||||
 | 
					      return !is_truthy(right);
 | 
				
			||||||
 | 
					    default: break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return Object{};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AstInterpreter::is_truthy(const Object &object) {
 | 
				
			||||||
 | 
					  switch (object.type) {
 | 
				
			||||||
 | 
					    case ObjectType::NIL: return false;
 | 
				
			||||||
 | 
					    case ObjectType::BOOL: {
 | 
				
			||||||
 | 
					      bool value = std::get<bool>(object.value);
 | 
				
			||||||
 | 
					      return value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default: return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								cclox_src/parser/interpreter.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								cclox_src/parser/interpreter.hh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					#ifndef AST_INTERPRETER_HH
 | 
				
			||||||
 | 
					#define AST_INTERPRETER_HH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "expr.hh"
 | 
				
			||||||
 | 
					#include "../tokenizer.hh"
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct AstInterpreter {
 | 
				
			||||||
 | 
					  void interpret(const Expr &expression);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  Object evaluate(const Expr &expr);
 | 
				
			||||||
 | 
					  Object evaluate_binary_expression(const Expr &expr);
 | 
				
			||||||
 | 
					  Object evaluate_grouping_expression(const Expr &expr);
 | 
				
			||||||
 | 
					  Object evaluate_literal_expression(const Expr &expr);
 | 
				
			||||||
 | 
					  Object evaluate_unary_expression(const Expr &expr);
 | 
				
			||||||
 | 
					  bool is_truthy(const Object &object);
 | 
				
			||||||
 | 
					  bool is_equal(const Object &left, const Object &right);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@@ -21,6 +21,35 @@ std::string Object::to_string() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Object::operator==(const Object &other) {
 | 
				
			||||||
 | 
					  if (type != other.type) {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  switch (type) {
 | 
				
			||||||
 | 
					    case ObjectType::NIL: return true;
 | 
				
			||||||
 | 
					    case ObjectType::BOOL: {
 | 
				
			||||||
 | 
					      bool left  = std::get<bool>(value);
 | 
				
			||||||
 | 
					      bool right = std::get<bool>(other.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return left == right;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ObjectType::IDENTIFIER:
 | 
				
			||||||
 | 
					    case ObjectType::STRING_LIT: {
 | 
				
			||||||
 | 
					      std::string left  = std::get<std::string>(value);
 | 
				
			||||||
 | 
					      std::string right = std::get<std::string>(other.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return left == right;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ObjectType::NUMBER: {
 | 
				
			||||||
 | 
					      double left  = std::get<double>(value);
 | 
				
			||||||
 | 
					      double right = std::get<double>(other.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return left == right;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::ostream &operator<<(std::ostream &os, const ObjectType &type) {
 | 
					std::ostream &operator<<(std::ostream &os, const ObjectType &type) {
 | 
				
			||||||
  switch (type) {
 | 
					  switch (type) {
 | 
				
			||||||
    case ObjectType::NIL:
 | 
					    case ObjectType::NIL:
 | 
				
			||||||
@@ -64,3 +93,7 @@ std::ostream &operator<<(std::ostream &os, const Object &obj) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return os;
 | 
					  return os;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Object::operator!=(const Object &other) {
 | 
				
			||||||
 | 
					  return !operator==(other);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,8 @@ struct Object {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      value{std::move(value)} {};
 | 
					      value{std::move(value)} {};
 | 
				
			||||||
  std::string to_string();
 | 
					  std::string to_string();
 | 
				
			||||||
 | 
					  bool operator==(const Object &other);
 | 
				
			||||||
 | 
					  bool operator!=(const Object &other);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ObjectType type;
 | 
					  ObjectType type;
 | 
				
			||||||
  std::variant<std::monostate, std::string, double, bool> value;
 | 
					  std::variant<std::monostate, std::string, double, bool> value;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user