Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C language support (prototype) #3347

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,35 @@ let buildLibraryRust() =
runInDir buildDir ("cargo fmt")
runInDir buildDir ("cargo build")

let buildLibraryC() =
let libraryDir = "src/fable-library-c"
let sourceDir = libraryDir </> "src"
let buildDir = "build/fable-library-c"
let fableLib = "."

let outDir = buildDir </> "src"

cleanDirs [buildDir]

runFableWithArgs sourceDir [
"--outDir " + resolveDir outDir
"--fableLib " + fableLib
"--lang C"
"--exclude Fable.Core"
"--define FABLE_LIBRARY"
]
// Copy *.lua from projectDir to buildDir
copyFiles sourceDir "*.c" outDir
copyDirRecursive libraryDir buildDir

runInDir buildDir ("gcc -v")
//runInDir buildDirLua ("lua ./setup.lua develop")

let buildCLibraryIfNotExists() =
let baseDir = __SOURCE_DIRECTORY__
if not (pathExists (baseDir </> "build/fable-library-c")) then
buildLibraryC()

let buildLibraryRustIfNotExists() =
if not (pathExists (__SOURCE_DIRECTORY__ </> "build/fable-library-rust")) then
buildLibraryRust()
Expand Down Expand Up @@ -554,7 +583,7 @@ let testRust testMode =
// limited cleanup to reduce IO churn, speed up rebuilds,
// and save the ssd (target folder can get huge)
cleanDirs [buildDir </> "src"]
cleanDirs [buildDir </> "tests"]
cleanDirs [buildDir </> "/"]
cleanDirs [buildDir </> ".fable"]

// copy rust only tests files (these must be present when running dotnet test as import expr tests for file presence)
Expand Down Expand Up @@ -590,6 +619,28 @@ let testRust testMode =
runInDir buildDir "cargo test"
runInDir buildDir "cargo test --features threaded"

let testC() =
buildCLibraryIfNotExists() // NOTE: fable-library-c needs to be built separately.

let projectDir = "tests/C"
let buildDir = "build/tests/C"

cleanDirs [buildDir </> "tests"]
copyDirRecursive ("build" </> "fable-library-c" </> "src") (buildDir </> "fable-lib")
// runInDir projectDir "dotnet test"
runFableWithArgs projectDir [
"--outDir " + buildDir
"--exclude Fable.Core"
"--lang C"
"--fableLib " + projectDir </> "fable-lib"
"--noCache"
]

// copyFile (projectDir </> "cunit.c") (buildDir </> "cunit.c")
// copyFile (projectDir </> "runtests.c") (buildDir </> "runtests.c")
runInDir buildDir "gcc ./tests/src/main.c -g" // -g gives debug symbols
runInDir buildDir "a.exe"

let testDart isWatch =
if not (pathExists "build/fable-library-dart") then
buildLibraryDart(true)
Expand Down Expand Up @@ -776,6 +827,7 @@ match BUILD_ARGS_LOWER with
| "test-rust-default"::_ -> testRust SingleThreaded
| "test-rust-threaded"::_ -> testRust MultiThreaded
| "test-rust-all"::_ -> testRust Everything
| "test-c"::_ -> testC()
| "test-dart"::_ -> testDart(false)
| "watch-test-dart"::_ -> testDart(true)

Expand Down Expand Up @@ -819,6 +871,7 @@ match BUILD_ARGS_LOWER with
| ("fable-library-ts"|"library-ts")::_ -> buildLibraryTs()
| ("fable-library-py"|"library-py")::_ -> buildLibraryPy()
| ("fable-library-rust" | "library-rust")::_ -> buildLibraryRust()
| ("fable-library-c" | "library-c")::_ -> buildLibraryC()
| ("fable-library-dart" | "library-dart")::_ ->
let clean = hasFlag "--no-clean" |> not
buildLibraryDart(clean)
Expand Down
2 changes: 2 additions & 0 deletions src/Fable.AST/Plugins.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Language =
| Php
| Dart
| Rust
| C

override this.ToString () =
match this with
Expand All @@ -25,6 +26,7 @@ type Language =
| Php -> "PHP"
| Dart -> "Dart"
| Rust -> "Rust"
| C -> "C"

type CompilerOptions =
{
Expand Down
3 changes: 3 additions & 0 deletions src/Fable.Cli/Entry.fs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ let argLanguage (args: CliArgs) =
| "php" -> Php
| "dart" -> Dart
| "rs" | "rust" -> Rust
| "C" | "c" -> C
| _ -> JavaScript)

type Runner =
Expand Down Expand Up @@ -236,6 +237,7 @@ type Runner =
| Python -> "FABLE_COMPILER_PYTHON"
| TypeScript -> "FABLE_COMPILER_TYPESCRIPT"
| JavaScript -> "FABLE_COMPILER_JAVASCRIPT"
| C -> "FABLE_COMPILER_C"
]
|> List.distinct

Expand Down Expand Up @@ -363,6 +365,7 @@ let getStatus = function
| Dart -> "beta"
| TypeScript -> "alpha"
| Php -> "experimental"
| C -> "experimental"

[<EntryPoint>]
let main argv =
Expand Down
19 changes: 19 additions & 0 deletions src/Fable.Cli/Pipeline.fs
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,29 @@ module Rust =
do! RustPrinter.run writer crate
}

module C =
open Fable.Transforms

let compileFile (com: Compiler) (cliArgs: CliArgs) pathResolver isSilent (outPath: string) = async {
//com.LibraryDir <- cliArgs.FableLibraryPath todo
let program =
FSharp2Fable.Compiler.transformFile com
|> FableTransforms.transformFile com
|> Fable2C.transformFile com

use headerWriter = new IO.StreamWriter(outPath.TrimEnd(".c".ToCharArray()) + ".h")
let ctxHeader = CPrinter.Output.Writer.create headerWriter
use fileWriter = new IO.StreamWriter(outPath)
let ctxFile = CPrinter.Output.Writer.create fileWriter
CPrinter.Output.writeHeaderFile ctxHeader program
CPrinter.Output.writeFile ctxFile program
}

let compileFile (com: Compiler) (cliArgs: CliArgs) pathResolver isSilent (outPath: string) =
match com.Options.Language with
| JavaScript | TypeScript -> Js.compileFile com cliArgs pathResolver isSilent outPath
| Python -> Python.compileFile com cliArgs pathResolver isSilent outPath
| Php -> Php.compileFile com cliArgs pathResolver isSilent outPath
| Dart -> Dart.compileFile com cliArgs pathResolver isSilent outPath
| Rust -> Rust.compileFile com cliArgs pathResolver isSilent outPath
| C -> C.compileFile com cliArgs pathResolver isSilent outPath
1 change: 1 addition & 0 deletions src/Fable.Cli/ProjectCracker.fs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ let getFableLibraryPath (opts: CrackerOptions) =
| _ -> "fable-library-py/fable_library", "fable_library"
| Dart -> "fable-library-dart", "fable_library"
| Rust -> "fable-library-rust", "fable-library-rust"
| C -> "fable-library-c", "fable-library-c"
| TypeScript -> "fable-library-ts", "fable-library-ts"
| _ -> "fable-library", "fable-library" + "." + Literals.VERSION

Expand Down
1 change: 1 addition & 0 deletions src/Fable.Cli/Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ module File =
| Fable.Dart -> ".dart"
| Fable.Rust -> ".rs"
| Fable.JavaScript -> ".js"
| Fable.C -> ".c"

match language, usesOutDir with
| Fable.Python, _ -> fileExt // Extension will always be .py for Python
Expand Down
108 changes: 108 additions & 0 deletions src/Fable.Transforms/C/C.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// fsharplint:disable MemberNames InterfaceNames

namespace rec Fable.AST.C


type CType =
| Int
| Char
| ShortInt
| UnsignedShortInt
| LongInt
| UnsignedLongInt
| Float
| Double
| Void
| Array of CType
| Pointer of CType
| CStruct of string
| Rc of CType
| CTypeDef of string


type Const =
| ConstInt16 of int16
| ConstInt32 of int32
| ConstString of string
| ConstBool of bool
| ConstNull


type CIdent =
{ Name: string; Type: CType }

type UnaryOp =
| Not
| RefOf

type BinaryOp =
| Equals
| Unequal
| Less
| LessOrEqual
| Greater
| GreaterOrEqual
| Multiply
| Divide
| Plus
| Minus
| BinaryTodo of string
| And
| Or




type Expr =
| Ident of CIdent
| Const of Const
| Unary of UnaryOp * Expr
| Binary of BinaryOp * Expr * Expr
| GetField of Expr * name: string
| GetFieldThroughPointer of Expr * name: string
| GetObjMethod of Expr * name: string
| GetAtIndex of Expr * idx: Expr
| SetValue of Expr * value: Expr
| SetExpr of Expr * Expr * value: Expr
| FunctionCall of f: Expr * args: Expr list
| Brackets of Expr
| Cast of CType * Expr
| AnonymousFunc of args: string list * body: Statement list
| Unknown of string
| Comment of string
| Macro of string * args: Expr list
| NoOp
// | Function of args: string list * body: Statement list
| NewArr of values: Expr list




type Statement =
// | FunctionDeclaration of name: string * args: string list * body: Statement list * returnType: CType
| DeclareIdent of name: string* assignType: CType
| Assignment of names: string list * Expr * assignType: CType
| Return of Expr * CType
| Do of Expr
| SNoOp
| ForLoop of string * start: Expr* limit: Expr* body: Statement list
| WhileLoop of guard: Expr * body: Statement list
| IfThenElse of guard: Expr * thenSt: Statement list * elseSt: Statement list

type Include =
{
Name: string
IsBuiltIn : bool
}

type Declaration =
| FunctionDeclaration of name: string * args: (string * CType) list * body: Statement list * returnType: CType
| StructDeclaration of name: string * params: (string * CType) list
| NothingDeclared
| TypedefFnDeclaration of name: string * args: (string * CType) list * returnType: CType

type File =
{ Filename: string
Includes: Include list
Declarations: Declaration list
ASTDebug: string }
Loading