summary refs log blame commit diff stats
path: root/lib/pure/punycode.nim
blob: ab6501ed182a12d3db27b4b8282fac1c57514ff7 (plain) (tree)
1
2
3
4
5
6
7
8







                                                   





































































































































































                                                                          
#
#
#            Nim's Runtime Library
#        (c) Copyright 2016 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

import strutils
import unicode

# issue #3045

const
  Base = 36
  TMin = 1
  TMax = 26
  Skew = 38
  Damp = 700
  InitialBias = 72
  InitialN = 128
  Delimiter = '-'

type
  PunyError* = object of Exception

proc decodeDigit(x: char): int {.raises: [PunyError].} =
  if '0' <= x and x <= '9':
    result = ord(x) - (ord('0') - 26)
  elif 'A' <= x and x <= 'Z':
    result = ord(x) - ord('A')
  elif 'a' <= x and x <= 'z':
    result = ord(x) - ord('a')
  else:
    raise newException(PunyError, "Bad input")

proc encodeDigit(digit: int): Rune {.raises: [PunyError].} =
  if 0 <= digit and digit < 26:
    result = Rune(digit + ord('a'))
  elif 26 <= digit and digit < 36:
    result = Rune(digit + (ord('0') - 26))
  else:
    raise newException(PunyError, "internal error in punycode encoding")

proc isBasic(c: char): bool = ord(c) < 0x80
proc isBasic(r: Rune): bool = int(r) < 0x80

proc adapt(delta, numPoints: int, first: bool): int =
  var d = if first: delta div Damp else: delta div 2
  d += d div numPoints
  var k = 0
  while d > ((Base-TMin)*TMax) div 2:
    d = d div (Base - TMin)
    k += Base
  result = k + (Base - TMin + 1) * d div (d + Skew)

proc encode*(prefix, s: string): string {.raises: [PunyError].} =
  ## Encode a string that may contain Unicode.
  ## Prepend `prefix` to the result
  result = prefix
  var (d, n, bias) = (0, InitialN, InitialBias)
  var (b, remaining) = (0, 0)
  for r in s.runes:
    if r.isBasic:
      # basic Ascii character
      inc b
      result.add($r)
    else:
      # special character
      inc remaining

  var h = b
  if b > 0:
    result.add(Delimiter) # we have some Ascii chars
  while remaining != 0:
    var m: int = high(int32)
    for r in s.runes:
      if m > int(r) and int(r) >= n:
        m = int(r)
    d += (m - n) * (h + 1)
    if d < 0:
      raise newException(PunyError, "invalid label " & s)
    n = m
    for r in s.runes:
      if int(r) < n:
        inc d
        if d < 0:
          raise newException(PunyError, "invalid label " & s)
        continue
      if int(r) > n:
        continue
      var q = d
      var k = Base
      while true:
        var t = k - bias
        if t < TMin:
          t = TMin
        elif t > TMax:
          t = TMax
        if q < t:
          break
        result.add($encodeDigit(t + (q - t) mod (Base - t)))
        q = (q - t) div (Base - t)
        k += Base
      result.add($encodeDigit(q))
      bias = adapt(d, h + 1, h == b)
      d = 0
      inc h
      dec remaining
    inc d
    inc n

proc encode*(s: string): string {.raises: [PunyError].} =
  ## Encode a string that may contain Unicode. Prefix is empty.
  result = encode("", s)

proc decode*(encoded: string): string {.raises: [PunyError].}  =
  ## Decode a Punycode-encoded string
  var
    n = InitialN
    i = 0
    bias = InitialBias
  var d = rfind(encoded, Delimiter)
  result = ""

  if d > 0:
    # found Delimiter
    for j in 0..<d:
      var c = encoded[j] # char
      if not c.isBasic:
        raise newException(PunyError, "Encoded contains a non-basic char")
      result.add(c) # add the character
    inc d
  else:
    d = 0 # set to first index

  while (d < len(encoded)):
    var oldi = i
    var w = 1
    var k = Base
    while true:
      if d == len(encoded):
        raise newException(PunyError, "Bad input: " & encoded)
      var c = encoded[d]; inc d
      var digit = int(decodeDigit(c))
      if digit > (high(int32) - i) div w:
        raise newException(PunyError, "Too large a value: " & $digit)
      i += digit * w
      var t: int
      if k <= bias:
        t = TMin
      elif k >= bias + TMax:
        t = TMax
      else:
        t = k - bias
      if digit < t:
        break
      w *= Base - t
      k += Base
    bias = adapt(i - oldi, runelen(result) + 1, oldi == 0)

    if i div (runelen(result) + 1) > high(int32) - n:
      raise newException(PunyError, "Value too large")

    n += i div (runelen(result) + 1)
    i = i mod (runelen(result) + 1)
    insert(result, $Rune(n), i)
    inc i

when isMainModule:
  assert(decode(encode("", "bücher")) == "bücher")
  assert(decode(encode("münchen")) == "münchen")
  assert encode("xn--", "münchen") == "xn--mnchen-3ya"
>
                                                               


                                                   

                                                          
                                                        

                           





















                                                                                





                                                                           






                                                   



                                                                               
                                                                 

                                                                 




                                                                     
 
                                                        
                                                        


                                           
#
#
#            Nim's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements the ability to access symbols from shared
## libraries. On POSIX this uses the ``dlsym`` mechanism, on
## Windows ``LoadLibrary``.
##
## Examples
## --------
##
## Loading a simple C function
## ^^^^^^^^^^^^^^^^^^^^^^^^^^^
##
## The following example demonstrates loading a function called 'greet'
## from a library that is determined at runtime based upon a language choice.
## If the library fails to load or the function 'greet' is not found,
## it quits with a failure error code.
##
## .. code-block::nim
##
##   import dynlib
##
##   type
##     greetFunction = proc(): cstring {.gcsafe, stdcall.}
##
##   let lang = stdin.readLine()
##
##   let lib = case lang
##   of "french":
##     loadLib("french.dll")
##   else:
##     loadLib("english.dll")
##
##   if lib == nil:
##     echo "Error loading library"
##     quit(QuitFailure)
##
##   let greet = cast[greetFunction](lib.symAddr("greet"))
##
##   if greet == nil:
##     echo "Error loading 'greet' function from library"
##     quit(QuitFailure)
##
##   let greeting = greet()
##
##   echo greeting
##
##   unloadLib(lib)
##

import strutils

type
  LibHandle* = pointer ## a handle to a dynamically loaded library

proc loadLib*(path: string, global_symbols=false): LibHandle {.gcsafe.}
  ## loads a library from `path`. Returns nil if the library could not
  ## be loaded.

proc loadLib*(): LibHandle {.gcsafe.}
  ## gets the handle from the current executable. Returns nil if the
  ## library could not be loaded.

proc unloadLib*(lib: LibHandle) {.gcsafe.}
  ## unloads the library `lib`

proc raiseInvalidLibrary*(name: cstring) {.noinline, noreturn.} =
  ## raises an `EInvalidLibrary` exception.
  var e: ref LibraryError
  new(e)
  e.msg = "could not find symbol: " & $name
  raise e

proc symAddr*(lib: LibHandle, name: cstring): pointer {.gcsafe.}
  ## retrieves the address of a procedure/variable from `lib`. Returns nil
  ## if the symbol could not be found.

proc checkedSymAddr*(lib: LibHandle, name: cstring): pointer =
  ## retrieves the address of a procedure/variable from `lib`. Raises
  ## `EInvalidLibrary` if the symbol could not be found.
  result = symAddr(lib, name)
  if result == nil: raiseInvalidLibrary(name)

proc libCandidates*(s: string, dest: var seq[string]) =
  ## given a library name pattern `s` write possible library names to `dest`.
  var le = strutils.find(s, '(')
  var ri = strutils.find(s, ')', le+1)
  if le >= 0 and ri > le:
    var prefix = substr(s, 0, le - 1)
    var suffix = substr(s, ri + 1)
    for middle in split(substr(s, le + 1, ri - 1), '|'):
      libCandidates(prefix & middle & suffix, dest)
  else:
    add(dest, s)

proc loadLibPattern*(pattern: string, global_symbols=false): LibHandle =
  ## loads a library with name matching `pattern`, similar to what `dlimport`
  ## pragma does. Returns nil if the library could not be loaded.
  ## Warning: this proc uses the GC and so cannot be used to load the GC.
  var candidates = newSeq[string]()
  libCandidates(pattern, candidates)
  for c in candidates:
    result = loadLib(c, global_symbols)
    if not result.isNil: break

when defined(posix):
  #
  # =========================================================================
  # This is an implementation based on the dlfcn interface.
  # The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD,
  # NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least
  # as an emulation layer on top of native functions.
  # =========================================================================
  #
  var
    RTLD_NOW {.importc: "RTLD_NOW", header: "<dlfcn.h>".}: int
    RTLD_GLOBAL {.importc: "RTLD_GLOBAL", header: "<dlfcn.h>".}: int

  proc dlclose(lib: LibHandle) {.importc, header: "<dlfcn.h>".}
  proc dlopen(path: cstring, mode: int): LibHandle {.
      importc, header: "<dlfcn.h>".}
  proc dlsym(lib: LibHandle, name: cstring): pointer {.
      importc, header: "<dlfcn.h>".}

  proc loadLib(path: string, global_symbols=false): LibHandle =
    var flags = RTLD_NOW
    if global_symbols: flags = flags or RTLD_GLOBAL
    return dlopen(path, flags)
  proc loadLib(): LibHandle = return dlopen(nil, RTLD_NOW)
  proc unloadLib(lib: LibHandle) = dlclose(lib)
  proc symAddr(lib: LibHandle, name: cstring): pointer =
    return dlsym(lib, name)

elif defined(nintendoswitch):
  #
  # =========================================================================
  # Nintendo switch DevkitPro sdk does not have these. Raise an error if called.
  # =========================================================================
  #

  proc dlclose(lib: LibHandle) =
    raise newException(OSError, "dlclose not implemented on Nintendo Switch!")
  proc dlopen(path: cstring, mode: int): LibHandle =
    raise newException(OSError, "dlopen not implemented on Nintendo Switch!")
  proc dlsym(lib: LibHandle, name: cstring): pointer =
    raise newException(OSError, "dlsym not implemented on Nintendo Switch!")
  proc loadLib(path: string, global_symbols=false): LibHandle =
    raise newException(OSError, "loadLib not implemented on Nintendo Switch!")
  proc loadLib(): LibHandle =
    raise newException(OSError, "loadLib not implemented on Nintendo Switch!")
  proc unloadLib(lib: LibHandle) =
    raise newException(OSError, "unloadLib not implemented on Nintendo Switch!")
  proc symAddr(lib: LibHandle, name: cstring): pointer =
    raise newException(OSError, "symAddr not implemented on Nintendo Switch!")

elif defined(windows) or defined(dos):
  #
  # =======================================================================
  # Native Windows Implementation
  # =======================================================================
  #
  when defined(cpp):
    type
      THINSTANCE {.importc: "HINSTANCE".} = object
        x: pointer
  else:
    type
      THINSTANCE {.importc: "HINSTANCE".} = pointer

  proc FreeLibrary(lib: THINSTANCE) {.importc, header: "<windows.h>", stdcall.}
  proc winLoadLibrary(path: cstring): THINSTANCE {.
      importc: "LoadLibraryA", header: "<windows.h>", stdcall.}
  proc getProcAddress(lib: THINSTANCE, name: cstring): pointer {.
      importc: "GetProcAddress", header: "<windows.h>", stdcall.}

  proc loadLib(path: string, global_symbols=false): LibHandle =
    result = cast[LibHandle](winLoadLibrary(path))
  proc loadLib(): LibHandle =
    result = cast[LibHandle](winLoadLibrary(nil))
  proc unloadLib(lib: LibHandle) = FreeLibrary(cast[THINSTANCE](lib))

  proc symAddr(lib: LibHandle, name: cstring): pointer =
    result = getProcAddress(cast[THINSTANCE](lib), name)

else:
  {.error: "no implementation for dynlib".}