Skip to content

Instantly share code, notes, and snippets.

@fdcastel
Last active March 30, 2016 07:27
Show Gist options
  • Save fdcastel/5a88332d61572947deb3 to your computer and use it in GitHub Desktop.
Save fdcastel/5a88332d61572947deb3 to your computer and use it in GitHub Desktop.
Fable Plugin to keep integer arithmetic in JavaScript
namespace Fable.Plugins
#r "../../build/fable/bin/Fable.exe"
open Fable.AST
open Fable.AST.Fable
open Fable.FSharp2Fable
type RandomPlugin() =
interface IReplacePlugin with
member x.TryReplace com (info: Fable.ApplyInfo) =
match info.ownerFullName with
| "Microsoft.FSharp.Core.ExtraTopLevelOperators"
| "Microsoft.FSharp.Core.Operators" ->
let patternFor t =
match t with
| PrimitiveType (Number kind) ->
match kind with
| Int8 -> Some "($0 + 0x80 & 0xFF) - 0x80"
| UInt8 -> Some "$0 & 0xFF"
| Int16 -> Some "($0 + 0x8000 & 0xFFFF) - 0x8000"
| UInt16 -> Some "$0 & 0xFFFF"
| Int32 -> Some "($0 + 0x80000000 >>> 0) - 0x80000000"
| UInt32 -> Some "$0 >>> 0"
| _ -> None
| _ -> None
let applyMask t args =
match patternFor t with
| Some pattern ->
pattern
|> Fable.Replacements.Util.emit info <| args
|> Some
| _ -> None
match info.methodName with
| "sbyte"
| "byte"
| "int8"
| "uint8"
| "int16"
| "uint16"
| "int"
| "int32"
| "uint"
| "uint32"
| "~~~"
| "~-"
| "~+" ->
if info.args.Length <> 1 then
failwithf "Unexpected arg count for '%s'" info.methodName
applyMask info.returnType info.args
| "+"
| "-"
| "*"
| "/"
| "%"
| "<<<"
| ">>>"
| "&&&"
| "|||"
| "^^^" ->
if info.args.Length <> 2 then
failwithf "Unexpected arg count for %s" info.methodName
match Fable.Replacements.Util.applyOp com info info.args info.methodName with
| Some expr -> applyMask info.returnType [expr]
| _ -> None
| _ -> None
| _ -> None
@alfonsogarciacaro
Copy link

The plugin looks fine :) I agree it should be easier to apply the mask after the operations instead of creating arrays for single elements. If you want to restrict arithmetic operations to the numeric types in the list it should be fine, but if you want to still support custom operators for types or such, you can add a wrapper to Fable.Replacements.Util.applyOp. Something like this:

        | "+" | "-" | "*" | "/" | "%"
        | "<<<" | ">>>" | "&&&" | "|||" | "^^^"
        | "~~~" | "~-" | "&&" | "||" ->
            let expr = applyOp com info info.args info.methodName
            match maskFor info.returnType with
            | Some mask ->
                sprintf "$0 & %s" mask
                |> Fable.Replacements.Util.emit info <| expr
                |> Some
            | None -> expr

Note that in this case maskFor would return None instead of throwing an exception. Also, you don't need braces in the template, because Babel will build a syntax tree from it preserving the operation order.

@fdcastel
Copy link
Author

Great tips! Working on it.

Also, I did somehow manage to completely miss the part you said about Babel managing the precedence order of operators. I see now you clearly state it in docs/plugins.md.

I know... I know... "More sleep, less coffee!" :)

@fdcastel
Copy link
Author

Second version. Now with Alfonso's suggestions and handling signed types.

@fdcastel
Copy link
Author

Third version. Fix a problem with signed int32.

& 0xFFFFFFFF preserves the signal (hence, does nothing :) ). Now uses >>> 0 to convert to unsigned.

Source: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators

@fdcastel
Copy link
Author

Fourth version. Adds support to Microsoft.FSharp.Core.ExtraTopLevelOperators.int8/uint8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment