diff --git a/hexa.json b/hexa.json index a953631..8eb0d4a 100644 --- a/hexa.json +++ b/hexa.json @@ -26,6 +26,10 @@ "data/project", "data/compilerError", "data/hints", + "toHexa/clang/clangTree", + "toHexa/clang/clangMinifier", + "toHexa/clang/clangGenerator", + "toHexa/clang/clangMain", "targets", "cli/args", "cli/help", diff --git a/source/cli.hexa b/source/cli.hexa index 06b9d27..ca4198b 100644 --- a/source/cli.hexa +++ b/source/cli.hexa @@ -1,5 +1,5 @@ // The Hexa Compiler -// Copyright (C) 2021-2023 Oleh Petrenko +// Copyright (C) 2021-2024 Oleh Petrenko // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -27,6 +27,10 @@ case "version": printVersion() case "symlink": symlink() case "init": init() + case "c2hexa": + // TODO add to help + process.argv.shift() + c2hexa(process.argv) // TODO `null` | "repl" opens REPL case _: try { diff --git a/source/toHexa/clang/clangGenerator.hexa b/source/toHexa/clang/clangGenerator.hexa new file mode 100644 index 0000000..590d86e --- /dev/null +++ b/source/toHexa/clang/clangGenerator.hexa @@ -0,0 +1,226 @@ +// The Hexa Compiler +// Copyright (C) 2024 Oleh Petrenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +// TODO some C++ +// TODO rename reserved keywords like `fun` +// TODO header-only mode to produce .hexa bindings +/// Actual code generator used by toHexa/Clang +class ClangGenerator { + new (project ClangProject) { + for file in project.files { + if not file.generate { + continue + } + + let node ClangNode = JSON.parse(Fs.readFileSync(file.input).toString()) + console.log('Found ' + node.inner.length + ' root syntax nodes.') + + let result [Node] = node.inner.filter(root => root.kind != null).map(root => nodeToNode(root)).filter(node => node != null) + + // TODO Would be much better just be allowed to `node.stringify()` static call where `fun stringify(node /*infer*/ = this)` thus allowing non-vTable @struct methods too! + let string = result.map(node => stringify(node)).join('\n\n') + // TODO run autoformat over the result + // TODO comment header + Fs.writeFileSync(file.output, string) + } + } + + fun toCamelCase(text: String): String { + if text.length == 1 { + return text.toLowerCase() + } + + return text.substr(0, 1).toLowerCase() + text.substr(1) + } + + fun toPascalCase(text: String): String { + if text.length == 1 { + return text.toUpperCase() + } + + return text.substr(0, 1).toUpperCase() + text.substr(1) + } + + fun typeOfReturn(of ClangType) ClangType { + return { + qualType: of.qualType.split(' (')[0] + } + } + + fun typeToType(of ClangType) NodeType { + return NodeType.Type(toPascalCase(of.qualType), null) + } + + fun nodeToNode(node ClangNode) Node? { + switch node.kind { + // Roots + case VarDecl: { + let name = toCamelCase(node.name) + let varType = typeToType(node.type) + var initializer Node? = null + + if let inner = node.inner, let value = inner[0] { + initializer = nodeToNode(value) + } + + return Node.Var(name, varType, initializer, false, false) + } + case RecordDecl: + case TypedefDecl: + case FunctionDecl: { + let name = toCamelCase(node.name) + let args [Node] = [] + var returnType NodeType = [] // TODO why [] is okay here + var returnType NodeType = typeToType(typeOfReturn(node.type)) + var body Node? = null + // TODO FORMAT + let nodes = node.inner ??([] as! [ClangNode] /*TODO*/) + + for functionDecl in nodes { + switch functionDecl.kind { + case ParmVarDecl: + args.push(Node.Var(toCamelCase(functionDecl.name), typeToType(functionDecl.type), null /*TODO*/, false, false)) + } + } + + for functionDecl in nodes { + switch functionDecl.kind { + case CompoundStmt: + body = nodeToNode(functionDecl) + // TODO CompoundStmt happens once? + case ParmVarDecl: // Ok + case _: // TODO + } + } + + return Node.Function(name, body, args, returnType, false /*TODO*/, false /*TODO*/) + } + + case IfStmt: + case DeclStmt: + case FieldDecl: + case BinaryOperator: + case CompoundAssignOperator: + case IntegerLiteral: + case WhileStmt: + case DoStmt: + case ForStmt: + case SEHTryStmt: + case SwitchStmt: + case CaseStmt: + case DefaultStmt: + case BreakStmt: + case GCCAsmStmt: + case NullStmt: + case ContinueStmt: + case UnaryOperator: + case ReturnStmt: + case CallExpr: + case ArraySubscriptExpr: + case CompoundStmt: + case UnaryExprOrTypeTraitExpr: + case CharacterLiteral: + case OffsetOfExpr: + case AtomicExpr: + case VAArgExpr: + case InitListExpr: + case CompoundLiteralExpr: + case ImplicitCastExpr: + case StringLiteral: + case ConstantExpr: + case CStyleCastExpr: + case DeclRefExpr: + case ParenExpr: + case ConditionalOperator: + case FloatingLiteral: + case MemberExpr: + case ParmVarDecl: + case TextComment: + case ParagraphComment: + case BlockCommandComment: + case ParamCommandComment: + case FullComment: + case BuiltinType: + case SEHExceptStmt: + case EnumDecl: + case EmptyDecl: return null // ; + case GotoStmt: + case LabelStmt: + case PredefinedExpr: + + case null: return null // Never happens + } + + return Node.String("// Unknown Clang node kind: " + node.kind) + } + + static fun stringify(node Node?) String { + switch node { + case String(s): return "'\(s)'" + case Ident(name): return name + case Bool(b): return b? "true" : "false" + case Int(s): return s.toString() + case Float(s): return s.toString() + case Null: return "null" + case This: return "this" + case Parenthesis(expr): return "(" + stringify(expr) + ")" + case Index(expr, index): return stringify(expr) + '[' + stringify(index) + ']' + case Dot(expr, name): return stringify(expr) + '.' + name + case DotUpper(expr, name): return stringify(expr) + '.' + name + case Call(e, args, argNames): + let arg = [] + for i in args.length { + if let name = argNames[i] { + arg.push(name + ': ' + stringify(args[i])) + } else { + arg.push(stringify(args[i])) + } + } + return stringify(e) + '(' + arg.join(', ') + ')' + case Array(elements): return '[' + [for el in elements stringify(el)].join(', ') + ']' + case Binop(a, op, b): return stringify(a) + ' ' + Token.stringify(op) + ' ' + stringify(b) + case Object(names, el): + return '{' + [for i in el.length names[i] + ': ' + stringify(el[i])].join(', ') + '}' + case NodeTypeValue(t): + return DataHelper.extractTypeName(t) + case Static(f): return 'static ' + stringify(f) + case Var(name, t, expr, const, external): { + var body = '' + if let expr = expr { + body = ' = ' + stringify(expr) + } + return + (external? 'declare ' : '') + + (const? 'let ' : 'var ') + name + ' ' + NodeType.stringify(t) + body + } + case Function(name, body, vars, returnType, external, variadic): + if name == null { + // TODO `// Anonymous` + return `fun` + } + + return 'fun ' + name + `(` + vars.map(v => switch v { + case Var(name, t, expr, const, external): + name + ' ' + NodeType.stringify(t) + }).join(', ') + `) ` + NodeType.stringify(returnType) + ' ' + stringify(body) + case null: + return '' + case _: + // TODO exhaustive + console.error('Cannot stringify', node) + return "..." + } + } +} diff --git a/source/toHexa/clang/clangMain.hexa b/source/toHexa/clang/clangMain.hexa new file mode 100644 index 0000000..382d38f --- /dev/null +++ b/source/toHexa/clang/clangMain.hexa @@ -0,0 +1,49 @@ +// The Hexa Compiler +// Copyright (C) 2024 Oleh Petrenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +/// Config file used by toHexa/Clang +interface ClangProject { + let files: [{ + input: String, + generate: Bool, + output: String + }] +} + +/// Entry point of the toHexa/Clang +fun c2hexa(arguments [String]) { + let action = arguments.shift() + + switch action { + case 'minify': + // TODO only-header only-source + let input = arguments.shift() + let output = arguments.shift() + if let input = input, let output = output { + // TODO + } else { + console.error(quote('Command syntax is `hexa c2hexa minify input.json output.json`')) + } + case 'json': + let input = arguments.shift() + if let input = input { + let json ClangProject = JSON.parse(Fs.readFileSync(input).toString()) + new ClangGenerator(json) + } else { + console.error(quote('Command syntax is `hexa c2hexa json project.json`')) + } + case _: console.error(quote('Unknown toHexa action `' + action + '`')) + } +} diff --git a/source/toHexa/clang/clangMinifier.hexa b/source/toHexa/clang/clangMinifier.hexa new file mode 100644 index 0000000..ae4b40c --- /dev/null +++ b/source/toHexa/clang/clangMinifier.hexa @@ -0,0 +1,14 @@ +// The Hexa Compiler +// Copyright (C) 2024 Oleh Petrenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . diff --git a/source/toHexa/clang/clangTree.hexa b/source/toHexa/clang/clangTree.hexa new file mode 100644 index 0000000..d98a3ae --- /dev/null +++ b/source/toHexa/clang/clangTree.hexa @@ -0,0 +1,105 @@ +// The Hexa Compiler +// Copyright (C) 2024 Oleh Petrenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +enum ClangKind : String { + IfStmt + DeclStmt + VarDecl + RecordDecl + FieldDecl + BinaryOperator + CompoundAssignOperator + IntegerLiteral + WhileStmt + DoStmt + ForStmt + SEHTryStmt + SwitchStmt + CaseStmt + DefaultStmt + BreakStmt + GCCAsmStmt + NullStmt + ContinueStmt + UnaryOperator + ReturnStmt + CallExpr + ArraySubscriptExpr + CompoundStmt + UnaryExprOrTypeTraitExpr + CharacterLiteral + OffsetOfExpr + AtomicExpr + VAArgExpr + InitListExpr + CompoundLiteralExpr + ImplicitCastExpr + StringLiteral + ConstantExpr + CStyleCastExpr + DeclRefExpr + ParenExpr + ConditionalOperator + FloatingLiteral + MemberExpr + ParmVarDecl + TextComment + ParagraphComment + BlockCommandComment + ParamCommandComment + FullComment + FunctionDecl + TypedefDecl + BuiltinType + SEHExceptStmt + EnumDecl + EmptyDecl + GotoStmt + LabelStmt + PredefinedExpr +} + +interface ClangType { + let qualType: String + let typeAliasDeclId: String? + let desugaredQualType: String? +} + +interface ClangNode { + let inner: [ClangNode] + let kind: ClangKind? + let type: ClangType? + let id: String + let name: String + let targetLabelDeclId: String + let declId: String + let mangledName: String + let text: String + let tagUsed: String + let param: String + let value: String // TODO | number + let opcode: String + let isImplicit: Bool + let hasElse: Bool + let referencedDecl: { + name: String + } + let loc: { + file: String + includedFrom: { + file: String + } + } +}