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

Add noIdentNormalize compilation flag #110

Merged
merged 7 commits into from
Jun 30, 2024
Merged
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ will be exported as well and documentation will be readable. This is mostly
useful if you want to export documentation but can't use `nodeclguards` (which
makes even more readable documentation).

### Preventing identifier normalization
By default, Futhark generates identifiers that are normalized per
[`strutils.nimIdentNormalize`](https://nim-lang.org/docs/strutils.html#nimIdentNormalize%2Cstring).
You might prefer keeping the case convention from your source library consistent
with your wrapper and in cases when the source name is a valid Nim identifier you can can use
`-d:noIdentNormalize`. For the cases when a source name is not a valid Nim identifier
this flag is ignored.

## Inline functions
When using Futhark with dynamic libraries it doesn't make sense to wrap inline
functions. However if you are compiling your code directly against some C code
Expand Down
49 changes: 44 additions & 5 deletions src/futhark.nim
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const
preAnsiFuncDecl = defined(preAnsiFuncDecl)
echoForwards = defined(echoForwards)
generateInline = defined(generateInline)
noIdentNormalize = defined(noIdentNormalize)
VERSION = block:
# source style, go up one dir
var nimblePath = currentSourcePath().parentDir().parentDir() / "futhark.nimble"
Expand Down Expand Up @@ -186,6 +187,39 @@ proc isUnsignedNumber(x: string): bool =
except ValueError:
result = false

const identStartChars = {'a'..'z', 'A'..'Z', char(0x80)..char(0xff)}
const identChars = identStartChars + {'0'..'9', '_'}

iterator span(s: string, startIndex: int, endIndex: int = 0): char =
# Span of characters in string `s`, starting at startIndex (inclusive)
# and ending at endIndex (exclusive). If endIndex is 0, then iterate to
# end of string.

var endIndex = if endIndex == 0:
s.len
else:
endIndex
for i in startIndex..<endIndex:
yield s[i]

proc isValidIdent(name: string): bool =
# Check for https://nim-lang.org/docs/manual.html#lexical-analysis-identifiers-amp-keywords

if name.len == 0:
return false
let firstChar = name[0]
let startCondition = firstChar in identStartChars
if not startCondition or name.len == 1:
return startCondition
var lastChar = firstChar
for c in name.span(1):
if (lastChar == c) and (c == '_'):
return false
if not (c in identChars):
return false
lastChar = c
lastChar != '_'

proc sanitizeName(usedNames: var HashSet[string], origName: string, kind: string, renameCallback: RenameCallback, partof = ""): string {.compileTime.} =
result = origName
if not renameCallback.isNil:
Expand All @@ -195,17 +229,22 @@ proc sanitizeName(usedNames: var HashSet[string], origName: string, kind: string
result = "compiler_" & result[2..^1]
else:
result = "internal_" & result[1..^1]
result = result.nimIdentNormalize()
var normalizedName = result.nimIdentNormalize()
if (not noIdentNormalize) or not result.isValidIdent:
result = normalizedName
var renamed = false
if usedNames.contains(result) or result in builtins:
if usedNames.contains(normalizedName) or result in builtins:
normalizedName.add kind
result.add kind
renamed = true
if usedNames.contains(result) or result in builtins:
result.add hash(origName).uint32.toHex
if usedNames.contains(normalizedName) or result in builtins:
let uniqueTail = hash(origName).uint32.toHex
result.add uniqueTail
normalizedName.add uniqueTail
renamed = true
if renamed:
hint "Renaming \"" & origName & "\" to \"" & result & "\"" & (if partof.len != 0: " in " & partof else: "")
usedNames.incl result
usedNames.incl normalizedName

proc sanitizeName(state: var State, origName: string, kind: string): string {.compileTime.} =
if not state.renamed.hasKey(origName):
Expand Down
2 changes: 2 additions & 0 deletions tests/tnoidentnormalize.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const int doNot_normalize = 1;
const int will__normalize = 2;
26 changes: 26 additions & 0 deletions tests/tnoidentnormalize.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os, strutils

import ../src/futhark

const outputPath = currentSourcePath.parentDir / "tnoidentnormalize_out.nim"

importc:
path "."
outputPath outputPath
"tnoidentnormalize.h"


let fObject = open(outputPath, FileMode.fmRead)
let outputText = fObject.readAll()
fObject.close()

# Because of Nim's case/underscore insensitivity, the output of `importc`
# has to be evaluated directly.

# The existence of the exported identifiers `doNot_normalize`, `willnormalize`,
# and the inexistence of exported `donotnormalize` should be sufficient in determining
# whether the test has passed.

doAssert " doNot_normalize*" in outputText
doAssert " willnormalize*" in outputText
doAssert (not (" donotnormalize*" in outputText))
1 change: 1 addition & 0 deletions tests/tnoidentnormalize.nims
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--define:noidentnormalize
7 changes: 7 additions & 0 deletions tests/tnoidentnormalizecollision.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#define my_var 1
#define myVar 2
#define myvar 3
#define MYVAR 4
#define MY_VAR 5
#define MyVar 6
#define My_Var 7
36 changes: 36 additions & 0 deletions tests/tnoidentnormalizecollision.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os, strutils

import ../src/futhark

const outputPath = currentSourcePath.parentDir / "tnoidentnormalizecollision_out.nim"

importc:
path "."
outputPath outputPath
"tnoidentnormalizecollision.h"


let fObject = open(outputPath, FileMode.fmRead)
let outputText = fObject.readAll()
fObject.close()

# Test default behavior per tnormalize.nim
doAssert(my_var == 1)
doAssert(myVarconst == 2) # Renamed as 1st definition collides
doAssert(myvarconstC690172C == 3) # Renamed as both 1st and 2nd definition collides
doAssert(MYVAR == 4)
doAssert(MY_VARconst == 5) # Renamed as 4th definition collides
doAssert(MyVarconst2E4AA817 == 6) # Renamed as 4th and 5th definition collides
doAssert(My_Varconst038D4D97 == 7) # Renamed as 4th and 5th definiton collides

# Manually test identifier case of output
doAssert " my_var*" in outputText
doAssert " myVarconst*" in outputText
doAssert " myvarconstC690172C*" in outputText
doAssert " MYVAR*" in outputText
doAssert " MyVarconst2E4AA817*" in outputText
doAssert " My_Varconst038D4D97*" in outputText

# `myvar` was renamed in the second name collision, so we can check its
# nonexistence in the output file.
doAssert (not (" myvar*" in outputText))
1 change: 1 addition & 0 deletions tests/tnoidentnormalizecollision.nims
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--define:noidentnormalize