japl/src/stdlib.nim

155 lines
6.0 KiB
Nim

# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Implementations of builtin functions and modules
import types/baseObject
import types/numbers
import types/methods
import types/typeutils
import types/japlString
import types/exception
import types/native
import times
import math
import strformat
import parseutils
import strutils
template join(args: seq[ptr Obj]): string =
## A template that returns the string
## representation of all args separated
## by a space.
var res = ""
for i in countup(0, args.high()):
let arg = args[i]
if i < args.high():
res = res & arg.stringify() & " "
else:
res = res & arg.stringify()
res
proc natPrint*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Native function print
## Prints an object representation
## to stdout. If more than one argument
## is passed, they will be printed separated
## by a space
# Note: we return nil and not asNil() because
# the VM will later use its own cached pointer
# to nil
echo join(args)
return (kind: retNative.Nil, result: nil)
proc natPrintErr*(args: seq[ptr Obj]): tuple[kind:
retNative, result: ptr Obj] =
## Native function printErr
## Prints an object representation
## to stderr. If more than one argument
## is passed, they will be printed separated
## by a space
writeLine stderr, join(args)
return (kind: retNative.Nil, result: nil)
proc natReadline*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Native function readline
## Reads a line from stdin and returns
## it as a string, optionally writing
## a given prompt to stdout
if args.len() > 1:
return (kind: retNative.Exception, result: newTypeError(&"Function 'readLine' takes 0 to 1 arguments, got {len(args)}"))
elif args.len() > 0 and not args[0].isStr():
return (kind: retNative.Exception, result: newTypeError(&"The prompt must be of type 'string', not '{args[0].typeName()}'"))
if args.len() > 0:
stdout.write(args[0].toStr())
return (kind: retNative.Object, result: stdin.readLine().asStr())
proc natClock*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Native function clock
## Returns the current unix
## time (also known as epoch)
## with subsecond precision
# TODO: Move this to a separate module once we have imports
result = (kind: retNative.Object, result: getTime().toUnixFloat().asFloat())
proc natRound*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Rounds a floating point number to a given
## precision (when precision == 0, this function drops the
## decimal part and returns an integer). Note that when
## precision > 0 and the value of the dropped digits
## exceeds or equals 5, the closest decimal place is
## increased by 1 (i.e. round(3.141519, 3) == 3.142)
var precision = 0
if len(args) notin 1..2:
# Here we need to return immediately to exit the procedure
return (kind: retNative.Exception, result: newTypeError(&"function 'round' takes from 1 to 2 arguments, got {len(args)}"))
elif len(args) == 2:
if not args[1].isInt():
return (kind: retNative.Exception, result: newTypeError(&"precision must be of type 'int', not '{args[1].typeName()}'"))
else:
precision = args[1].toInt()
if args[0].kind notin {ObjectType.Integer, ObjectType.Float}:
return (kind: retNative.Exception, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
if precision < 0:
result = (kind: retNative.Exception, result: newTypeError(&"precision must be positive"))
else:
if args[0].isInt():
result = (kind: retNative.Object, result: args[0])
elif precision == 0:
result = (kind: retNative.Object, result: int(args[0].toFloat()).asInt())
else:
result = (kind: retNative.Object, result: round(args[0].toFloat(), precision).asFloat())
proc natToInt*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Drops the decimal part of a float and returns an integer or
## converts an integer string to an actual integer object.
## If the value is already an integer, the same object is returned
if args[0].isInt():
result = (kind: retNative.Object, result: args[0])
elif args[0].isFloat():
result = (kind: retNative.Object, result: int(args[0].toFloat()).asInt())
elif args[0].isStr():
let s = args[0].toStr()
for c in s:
if not c.isDigit():
return (kind: retNative.Exception, result: newValueError("invalid argument"))
try:
var num: int
discard parseInt(args[0].toStr(), num)
result = (kind: retNative.Object, result: num.asInt())
except ValueError:
result = (kind: retNative.Exception, result: newValueError("invalid argument"))
else:
result = (kind: retNative.Exception, result: newTypeError(&"input must be of type 'int', 'float' or 'string', not '{args[0].typeName()}'"))
proc natType*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Returns the type of a given object as a string
result = (kind: retNative.Object, result: args[0].typeName().asStr())
proc natToString*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Returns the string representation of an object
result = (kind: retNative.Object, result: args[0].stringify().asStr())