Compare commits
5 Commits
7ebc27cab9
...
4b9f58b34f
| Author | SHA1 | Date | |
|---|---|---|---|
|
4b9f58b34f
|
|||
|
a420ebf69f
|
|||
|
e1ffa8a11d
|
|||
|
2126b03069
|
|||
|
47e8b1bf51
|
@@ -116,10 +116,16 @@ struct var_decl_node final : public decl_node {
|
|||||||
struct func_decl_node final : public decl_node {
|
struct func_decl_node final : public decl_node {
|
||||||
decl_type_e decl_type() const override { return Function; }
|
decl_type_e decl_type() const override { return Function; }
|
||||||
|
|
||||||
|
struct def_s {
|
||||||
|
comp_stmt_node body;
|
||||||
|
std::vector<expr_node*> preConds;
|
||||||
|
std::vector<expr_node*> postConds;
|
||||||
|
};
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
ast_type type;
|
ast_type type;
|
||||||
std::vector<var_decl_node> params;
|
std::vector<var_decl_node> params;
|
||||||
std::optional<comp_stmt_node> body;
|
std::optional<def_s> def;
|
||||||
};
|
};
|
||||||
|
|
||||||
class expr_node : public stmt_node {
|
class expr_node : public stmt_node {
|
||||||
@@ -128,9 +134,11 @@ public:
|
|||||||
Literal,
|
Literal,
|
||||||
|
|
||||||
VarRead,
|
VarRead,
|
||||||
|
FunctionCall,
|
||||||
Group,
|
Group,
|
||||||
BinaryOp,
|
BinaryOp,
|
||||||
UnaryOp,
|
UnaryOp,
|
||||||
|
If,
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
category_e category() const override { return ast_node_cat::Expression; }
|
category_e category() const override { return ast_node_cat::Expression; }
|
||||||
@@ -149,6 +157,13 @@ struct var_read_expr_node final : public expr_node {
|
|||||||
: name(std::move(name)) {}
|
: name(std::move(name)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct func_call_expr_node final : public expr_node {
|
||||||
|
expr_type_e expr_type() const override { return FunctionCall; }
|
||||||
|
|
||||||
|
expr_node* lhs = nullptr;
|
||||||
|
std::vector<expr_node*> args;
|
||||||
|
};
|
||||||
|
|
||||||
struct group_expr_node final : public expr_node {
|
struct group_expr_node final : public expr_node {
|
||||||
expr_type_e expr_type() const override { return Group; }
|
expr_type_e expr_type() const override { return Group; }
|
||||||
|
|
||||||
@@ -208,6 +223,14 @@ struct unary_op_expr_node final : public expr_node {
|
|||||||
unary_op_type type = Positive;
|
unary_op_type type = Positive;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct if_expr_node final : public expr_node {
|
||||||
|
expr_type_e expr_type() const override { return If; }
|
||||||
|
|
||||||
|
expr_node* cond = nullptr;
|
||||||
|
expr_node* thenExpr = nullptr;
|
||||||
|
expr_node* elseExpr = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
class lit_node : public expr_node {
|
class lit_node : public expr_node {
|
||||||
public:
|
public:
|
||||||
enum lit_type_e {
|
enum lit_type_e {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "furc/front/token.hpp"
|
#include "furc/front/token.hpp"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <optional>
|
#include <deque>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
namespace furc {
|
namespace furc {
|
||||||
@@ -23,9 +23,10 @@ public:
|
|||||||
lexer& operator=(const lexer&) = delete;
|
lexer& operator=(const lexer&) = delete;
|
||||||
public:
|
public:
|
||||||
token next_token();
|
token next_token();
|
||||||
token peek_token();
|
token peek_token(std::size_t offset = 0);
|
||||||
token skip_token();
|
|
||||||
private:
|
private:
|
||||||
|
token get_token();
|
||||||
|
|
||||||
void next();
|
void next();
|
||||||
constexpr char get(std::size_t offset = 0) const;
|
constexpr char get(std::size_t offset = 0) const;
|
||||||
void skip_spaces();
|
void skip_spaces();
|
||||||
@@ -38,7 +39,7 @@ private:
|
|||||||
std::size_t m_row = 0;
|
std::size_t m_row = 0;
|
||||||
std::size_t m_lineStart = 0;
|
std::size_t m_lineStart = 0;
|
||||||
|
|
||||||
std::optional<token> m_peekToken;
|
std::deque<token> m_peekToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace furc
|
} // namespace furc
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ struct token {
|
|||||||
While, /**< `while` */
|
While, /**< `while` */
|
||||||
Public, /**< `public` */
|
Public, /**< `public` */
|
||||||
Private, /**< `private` */
|
Private, /**< `private` */
|
||||||
|
Pre, /**< `pre` */
|
||||||
|
Post, /**< `post` */
|
||||||
|
|
||||||
Pointerof, /**< `pointerof` */
|
Pointerof, /**< `pointerof` */
|
||||||
Sizeof, /**< `sizeof` */
|
Sizeof, /**< `sizeof` */
|
||||||
@@ -186,6 +188,8 @@ struct token {
|
|||||||
case token::While: return os << "while";
|
case token::While: return os << "while";
|
||||||
case token::Public: return os << "public";
|
case token::Public: return os << "public";
|
||||||
case token::Private: return os << "private";
|
case token::Private: return os << "private";
|
||||||
|
case token::Pre: return os << "pre";
|
||||||
|
case token::Post: return os << "post";
|
||||||
case token::Pointerof: return os << "pointerof";
|
case token::Pointerof: return os << "pointerof";
|
||||||
case token::Sizeof: return os << "sizeof";
|
case token::Sizeof: return os << "sizeof";
|
||||||
case token::Lengthof: return os << "lengthof";
|
case token::Lengthof: return os << "lengthof";
|
||||||
|
|||||||
+22
-17
@@ -8,12 +8,23 @@
|
|||||||
namespace furc {
|
namespace furc {
|
||||||
|
|
||||||
token lexer::next_token() {
|
token lexer::next_token() {
|
||||||
if (m_peekToken.has_value()) {
|
if (m_peekToken.empty()) return get_token();
|
||||||
auto tok = m_peekToken.value();
|
auto tok = m_peekToken.front();
|
||||||
m_peekToken = {};
|
m_peekToken.pop_front();
|
||||||
return tok;
|
return tok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token lexer::peek_token(std::size_t offset) {
|
||||||
|
if (m_peekToken.size() <= offset) {
|
||||||
|
while (m_peekToken.size() <= offset) {
|
||||||
|
auto tok = get_token();
|
||||||
|
m_peekToken.push_back(tok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_peekToken[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
token lexer::get_token() {
|
||||||
skip_spaces();
|
skip_spaces();
|
||||||
|
|
||||||
if (m_cursor >= m_content.size()) return { location(), token::EndOfFile };
|
if (m_cursor >= m_content.size()) return { location(), token::EndOfFile };
|
||||||
@@ -52,6 +63,8 @@ token lexer::next_token() {
|
|||||||
{ "while", token::While },
|
{ "while", token::While },
|
||||||
{ "public", token::Public },
|
{ "public", token::Public },
|
||||||
{ "private", token::Private },
|
{ "private", token::Private },
|
||||||
|
{ "pre", token::Pre },
|
||||||
|
{ "post", token::Post },
|
||||||
{ "pointerof", token::Pointerof },
|
{ "pointerof", token::Pointerof },
|
||||||
{ "sizeof", token::Sizeof },
|
{ "sizeof", token::Sizeof },
|
||||||
{ "lengthof", token::Lengthof },
|
{ "lengthof", token::Lengthof },
|
||||||
@@ -169,18 +182,6 @@ token lexer::next_token() {
|
|||||||
return { loc, token::UnexpectedCharacter, get() };
|
return { loc, token::UnexpectedCharacter, get() };
|
||||||
}
|
}
|
||||||
|
|
||||||
token lexer::peek_token() {
|
|
||||||
if (m_peekToken.has_value()) return m_peekToken.value();
|
|
||||||
auto tok = next_token();
|
|
||||||
m_peekToken = tok;
|
|
||||||
return tok;
|
|
||||||
}
|
|
||||||
|
|
||||||
token lexer::skip_token() {
|
|
||||||
m_peekToken = {};
|
|
||||||
return next_token();
|
|
||||||
}
|
|
||||||
|
|
||||||
void lexer::next() {
|
void lexer::next() {
|
||||||
if (m_cursor < m_content.size()) ++m_cursor;
|
if (m_cursor < m_content.size()) ++m_cursor;
|
||||||
}
|
}
|
||||||
@@ -190,8 +191,12 @@ constexpr char lexer::get(std::size_t offset) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void lexer::skip_spaces() {
|
void lexer::skip_spaces() {
|
||||||
while (m_cursor < m_content.size() && std::isspace(get()) != 0)
|
while (m_cursor < m_content.size() && std::isspace(get()) != 0) {
|
||||||
++m_cursor;
|
if (m_content[m_cursor++] == '\n') {
|
||||||
|
++m_row;
|
||||||
|
m_lineStart = m_cursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr token::location lexer::location() const {
|
constexpr token::location lexer::location() const {
|
||||||
|
|||||||
+153
-31
@@ -58,11 +58,81 @@ stmt_node* parser::parse_stmt() {
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (m_lexer.peek_token().type == token::Func) {
|
||||||
return parse_decl();
|
eat_token(token::Func);
|
||||||
} catch (...) {
|
func_decl_node func;
|
||||||
return parse_expr();
|
|
||||||
|
func.name = std::string(eat_token(token::Identifier).value.string);
|
||||||
|
eat_token(token::LParen);
|
||||||
|
if (m_lexer.peek_token().type != token::RParen) {
|
||||||
|
do {
|
||||||
|
var_decl_node param;
|
||||||
|
|
||||||
|
param.name = std::string(eat_token(token::Identifier).value.string);
|
||||||
|
eat_token(token::Colon);
|
||||||
|
param.type = parse_type();
|
||||||
|
if (m_lexer.peek_token().type == token::Equals) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
param.init = parse_expr();
|
||||||
}
|
}
|
||||||
|
func.params.emplace_back(std::move(param));
|
||||||
|
} while (eat_token(token::Comma, token::RParen).type == token::Comma);
|
||||||
|
} else {
|
||||||
|
eat_token(token::RParen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::SlimArrow) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
func.type = parse_type();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::Semicolon) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
func_decl_node::def_s def;
|
||||||
|
|
||||||
|
while (m_lexer.peek_token().type == token::Pre || m_lexer.peek_token().type == token::Post) {
|
||||||
|
auto token = m_lexer.next_token();
|
||||||
|
eat_token(token::LParen);
|
||||||
|
auto* expr = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
|
||||||
|
switch (token.type) {
|
||||||
|
case token::Pre: {
|
||||||
|
def.preConds.push_back(expr);
|
||||||
|
} break;
|
||||||
|
case token::Post: {
|
||||||
|
def.postConds.push_back(expr);
|
||||||
|
} break;
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def.body = parse_comp();
|
||||||
|
func.def = std::move(def);
|
||||||
|
|
||||||
|
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::Identifier &&
|
||||||
|
(m_lexer.peek_token(1).type == token::Colon || m_lexer.peek_token(1).type == token::Equals)) {
|
||||||
|
var_decl_node var;
|
||||||
|
var.name = std::string(eat_token(token::Identifier).value.string);
|
||||||
|
eat_token(token::Colon); // TODO: Auto-deduce the type
|
||||||
|
var.type = parse_type();
|
||||||
|
if (eat_token(token::Equals, token::Semicolon).type == token::Equals) {
|
||||||
|
var.init = parse_expr();
|
||||||
|
eat_token(token::Semicolon);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_arena->allocate<var_decl_node>(std::move(var));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* expr = parse_expr();
|
||||||
|
eat_token(token::Semicolon);
|
||||||
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
decl_node* parser::parse_decl() {
|
decl_node* parser::parse_decl() {
|
||||||
@@ -99,7 +169,27 @@ decl_node* parser::parse_decl() {
|
|||||||
return m_arena->allocate<func_decl_node>(std::move(func));
|
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||||
}
|
}
|
||||||
|
|
||||||
func.body = parse_comp();
|
func_decl_node::def_s def;
|
||||||
|
|
||||||
|
while (m_lexer.peek_token().type == token::Pre || m_lexer.peek_token().type == token::Post) {
|
||||||
|
auto token = m_lexer.next_token();
|
||||||
|
eat_token(token::LParen);
|
||||||
|
auto* expr = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
|
||||||
|
switch (token.type) {
|
||||||
|
case token::Pre: {
|
||||||
|
def.preConds.push_back(expr);
|
||||||
|
} break;
|
||||||
|
case token::Post: {
|
||||||
|
def.postConds.push_back(expr);
|
||||||
|
} break;
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def.body = parse_comp();
|
||||||
|
func.def = std::move(def);
|
||||||
|
|
||||||
return m_arena->allocate<func_decl_node>(std::move(func));
|
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||||
}
|
}
|
||||||
@@ -149,7 +239,7 @@ comp_stmt_node parser::parse_comp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expr_node* parser::parse_expr_primary() {
|
expr_node* parser::parse_expr_primary() {
|
||||||
auto token = eat_token(token::Identifier, token::LParen, token::Integer, token::Char);
|
auto token = eat_token(token::Identifier, token::LParen, token::If, token::Integer, token::Char);
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case token::Identifier: {
|
case token::Identifier: {
|
||||||
return m_arena->allocate<var_read_expr_node>(std::string(token.value.string));
|
return m_arena->allocate<var_read_expr_node>(std::string(token.value.string));
|
||||||
@@ -160,6 +250,16 @@ expr_node* parser::parse_expr_primary() {
|
|||||||
eat_token(token::RParen);
|
eat_token(token::RParen);
|
||||||
return m_arena->allocate<group_expr_node>(std::move(group));
|
return m_arena->allocate<group_expr_node>(std::move(group));
|
||||||
}
|
}
|
||||||
|
case token::If: {
|
||||||
|
if_expr_node ifExpr;
|
||||||
|
eat_token(token::LParen);
|
||||||
|
ifExpr.cond = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
ifExpr.thenExpr = parse_expr();
|
||||||
|
eat_token(token::Else);
|
||||||
|
ifExpr.elseExpr = parse_expr();
|
||||||
|
return m_arena->allocate<if_expr_node>(std::move(ifExpr));
|
||||||
|
}
|
||||||
case token::Integer: {
|
case token::Integer: {
|
||||||
return m_arena->allocate<int_lit_node>(int_lit_node(token.value.integer));
|
return m_arena->allocate<int_lit_node>(int_lit_node(token.value.integer));
|
||||||
}
|
}
|
||||||
@@ -214,30 +314,37 @@ expr_node* parser::parse_expr_unary() {
|
|||||||
|
|
||||||
expr_node* parser::parse_expr_right(expr_node* lhs, std::uint32_t precedence) {
|
expr_node* parser::parse_expr_right(expr_node* lhs, std::uint32_t precedence) {
|
||||||
struct op_info {
|
struct op_info {
|
||||||
binary_op_expr_node::binary_op_type type;
|
enum type_e {
|
||||||
|
Binary = 0,
|
||||||
|
FunctionCall,
|
||||||
|
} type;
|
||||||
std::uint32_t precedence;
|
std::uint32_t precedence;
|
||||||
|
union {
|
||||||
|
binary_op_expr_node::binary_op_type binary;
|
||||||
|
};
|
||||||
bool right = false;
|
bool right = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<token_t, op_info> s_ops = {
|
static std::unordered_map<token_t, op_info> s_ops = {
|
||||||
{ token::Plus, { binary_op_expr_node::Add, 4 } },
|
{ token::Plus, { op_info::Binary, 4, binary_op_expr_node::Add } },
|
||||||
{ token::Minus, { binary_op_expr_node::Sub, 4 } },
|
{ token::Minus, { op_info::Binary, 4, binary_op_expr_node::Sub } },
|
||||||
{ token::Star, { binary_op_expr_node::Mul, 3 } },
|
{ token::Star, { op_info::Binary, 3, binary_op_expr_node::Mul } },
|
||||||
{ token::Slash, { binary_op_expr_node::Div, 3 } },
|
{ token::Slash, { op_info::Binary, 3, binary_op_expr_node::Div } },
|
||||||
{ token::Percent, { binary_op_expr_node::Mod, 3 } },
|
{ token::Percent, { op_info::Binary, 3, binary_op_expr_node::Mod } },
|
||||||
{ token::DblLT, { binary_op_expr_node::Shl, 5 } },
|
{ token::DblLT, { op_info::Binary, 5, binary_op_expr_node::Shl } },
|
||||||
{ token::DblGT, { binary_op_expr_node::Shr, 5 } },
|
{ token::DblGT, { op_info::Binary, 5, binary_op_expr_node::Shr } },
|
||||||
{ token::Ampersand, { binary_op_expr_node::BinAnd, 8 } },
|
{ token::Ampersand, { op_info::Binary, 8, binary_op_expr_node::BinAnd } },
|
||||||
{ token::Pipe, { binary_op_expr_node::BinOr, 10 } },
|
{ token::Pipe, { op_info::Binary, 10, binary_op_expr_node::BinOr } },
|
||||||
{ token::Hat, { binary_op_expr_node::BinXor, 9 } },
|
{ token::Hat, { op_info::Binary, 9, binary_op_expr_node::BinXor } },
|
||||||
{ token::DblAmpersand, { binary_op_expr_node::And, 11 } },
|
{ token::DblAmpersand, { op_info::Binary, 11, binary_op_expr_node::And } },
|
||||||
{ token::DblPipe, { binary_op_expr_node::Or, 12 } },
|
{ token::DblPipe, { op_info::Binary, 12, binary_op_expr_node::Or } },
|
||||||
{ token::DblEquals, { binary_op_expr_node::Equals, 7 } },
|
{ token::DblEquals, { op_info::Binary, 7, binary_op_expr_node::Equals } },
|
||||||
{ token::ExEquals, { binary_op_expr_node::NotEquals, 7 } },
|
{ token::ExEquals, { op_info::Binary, 7, binary_op_expr_node::NotEquals } },
|
||||||
{ token::LessThan, { binary_op_expr_node::LessThan, 6 } },
|
{ token::LessThan, { op_info::Binary, 6, binary_op_expr_node::LessThan } },
|
||||||
{ token::LessEquals, { binary_op_expr_node::LessEquals, 6 } },
|
{ token::LessEquals, { op_info::Binary, 6, binary_op_expr_node::LessEquals } },
|
||||||
{ token::GreaterThan, { binary_op_expr_node::GreaterThan, 6 } },
|
{ token::GreaterThan, { op_info::Binary, 6, binary_op_expr_node::GreaterThan } },
|
||||||
{ token::GreaterEquals, { binary_op_expr_node::GreaterEquals, 6 } },
|
{ token::GreaterEquals, { op_info::Binary, 6, binary_op_expr_node::GreaterEquals } },
|
||||||
|
{ token::LParen, { op_info::FunctionCall, 1 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -246,18 +353,33 @@ expr_node* parser::parse_expr_right(expr_node* lhs, std::uint32_t precedence) {
|
|||||||
auto op = it->second;
|
auto op = it->second;
|
||||||
m_lexer.next_token();
|
m_lexer.next_token();
|
||||||
|
|
||||||
expr_node* rhs = parse_expr_unary();
|
if (op.type == op_info::FunctionCall) {
|
||||||
auto nextIt = s_ops.find(m_lexer.peek_token().type);
|
func_call_expr_node funcCall;
|
||||||
if (nextIt != s_ops.end()) {
|
funcCall.lhs = lhs;
|
||||||
rhs = parse_expr_right(rhs, op.precedence + (op.right ? 1 : 0));
|
|
||||||
|
if (m_lexer.peek_token().type != token::RParen) {
|
||||||
|
do {
|
||||||
|
funcCall.args.push_back(parse_expr());
|
||||||
|
} while (eat_token(token::Comma, token::RParen).type == token::Comma);
|
||||||
|
} else {
|
||||||
|
m_lexer.next_token();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lhs = m_arena->allocate<func_call_expr_node>(std::move(funcCall));
|
||||||
|
} else {
|
||||||
binary_op_expr_node binary;
|
binary_op_expr_node binary;
|
||||||
binary.lhs = lhs;
|
binary.lhs = lhs;
|
||||||
binary.rhs = rhs;
|
binary.rhs = parse_expr_unary();
|
||||||
binary.type = op.type;
|
binary.type = op.binary;
|
||||||
|
|
||||||
|
auto nextIt = s_ops.find(m_lexer.peek_token().type);
|
||||||
|
if (nextIt != s_ops.end()) {
|
||||||
|
binary.rhs = parse_expr_right(binary.rhs, op.precedence + (op.right ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
lhs = m_arena->allocate<binary_op_expr_node>(std::move(binary));
|
lhs = m_arena->allocate<binary_op_expr_node>(std::move(binary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace furc
|
} // namespace furc
|
||||||
|
|||||||
+3
-3
@@ -6,10 +6,10 @@ int main(void) {
|
|||||||
furlang::arena arena;
|
furlang::arena arena;
|
||||||
|
|
||||||
std::string_view content = R"(
|
std::string_view content = R"(
|
||||||
func main(argc: u64) -> s32 {
|
func main(argc: u64) -> s32 pre(arc > 1) {
|
||||||
x: s32 = 1 + 2 * 3;
|
x: s32 = 1 + 2 * 3;
|
||||||
if (x == 9) return 1;
|
println(x);
|
||||||
else return 0;
|
return if (x == 9) 1 else 0;
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user