mirror of https://github.com/japl-lang/japl.git
Added the retNative enum for native functions to handle singletons properly, minor refactoring in the parser and the vm
This commit is contained in:
parent
c882281944
commit
3eeffabe7d
2
build.py
2
build.py
|
@ -209,7 +209,7 @@ if __name__ == "__main__":
|
||||||
logging.error("Invalid parameter for --options")
|
logging.error("Invalid parameter for --options")
|
||||||
exit()
|
exit()
|
||||||
build(args.path, flags, options, args.override_config, args.skip_tests)
|
build(args.path, flags, options, args.override_config, args.skip_tests)
|
||||||
if not args.keep_results_file:
|
if not args.keep_results_file and not args.skip_tests:
|
||||||
try:
|
try:
|
||||||
os.remove("testresults.txt")
|
os.remove("testresults.txt")
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
|
|
@ -20,12 +20,10 @@ import parseopt
|
||||||
import os
|
import os
|
||||||
import config
|
import config
|
||||||
import vm
|
import vm
|
||||||
import stdlib
|
|
||||||
|
|
||||||
|
|
||||||
proc repl() =
|
proc repl() =
|
||||||
var bytecodeVM = initVM()
|
var bytecodeVM = initVM()
|
||||||
bytecodeVM.stdlibInit()
|
|
||||||
echo JAPL_VERSION_STRING
|
echo JAPL_VERSION_STRING
|
||||||
echo &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
|
echo &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
|
@ -65,6 +63,7 @@ proc main(file: var string = "", fromString: bool = false) =
|
||||||
var source: string
|
var source: string
|
||||||
if file == "":
|
if file == "":
|
||||||
repl()
|
repl()
|
||||||
|
return # We exit after the REPL has ran
|
||||||
elif not fromString:
|
elif not fromString:
|
||||||
var sourceFile: File
|
var sourceFile: File
|
||||||
try:
|
try:
|
||||||
|
@ -80,7 +79,6 @@ proc main(file: var string = "", fromString: bool = false) =
|
||||||
source = file
|
source = file
|
||||||
file = "<string>"
|
file = "<string>"
|
||||||
var bytecodeVM = initVM()
|
var bytecodeVM = initVM()
|
||||||
bytecodeVM.stdlibInit()
|
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
echo "Debugger enabled, expect verbose output\n"
|
echo "Debugger enabled, expect verbose output\n"
|
||||||
echo "==== VM Constants ====\n"
|
echo "==== VM Constants ====\n"
|
||||||
|
|
|
@ -205,9 +205,6 @@ proc scanToken(self: Lexer) =
|
||||||
return
|
return
|
||||||
elif single == '\n':
|
elif single == '\n':
|
||||||
self.line += 1
|
self.line += 1
|
||||||
# TODO: Fix this to only emit semicolons where needed
|
|
||||||
# if self.tokens[^1].kind != TokenType.SEMICOLON:
|
|
||||||
# self.tokens.add(self.createToken(TOKENS[';']))
|
|
||||||
elif single in ['"', '\'']:
|
elif single in ['"', '\'']:
|
||||||
self.parseString(single)
|
self.parseString(single)
|
||||||
elif single.isDigit():
|
elif single.isDigit():
|
||||||
|
@ -247,8 +244,6 @@ proc lex*(self: Lexer): seq[Token] =
|
||||||
while not self.done():
|
while not self.done():
|
||||||
self.start = self.current
|
self.start = self.current
|
||||||
self.scanToken()
|
self.scanToken()
|
||||||
# if self.tokens[^1].kind != TokenType.SEMICOLON:
|
|
||||||
# self.tokens.add(self.createToken(TOKENS[';']))
|
|
||||||
self.tokens.add(Token(kind: TokenType.EOF, lexeme: "EOF", line: self.line))
|
self.tokens.add(Token(kind: TokenType.EOF, lexeme: "EOF", line: self.line))
|
||||||
return self.tokens
|
return self.tokens
|
||||||
|
|
||||||
|
|
|
@ -14,20 +14,20 @@
|
||||||
|
|
||||||
# Implementations of builtin functions and modules
|
# Implementations of builtin functions and modules
|
||||||
|
|
||||||
import vm
|
|
||||||
import types/native
|
|
||||||
import types/baseObject
|
import types/baseObject
|
||||||
import types/numbers
|
import types/numbers
|
||||||
import types/methods
|
import types/methods
|
||||||
import types/japlString
|
import types/japlString
|
||||||
import types/exception
|
import types/exception
|
||||||
|
import types/native
|
||||||
|
|
||||||
import times
|
import times
|
||||||
import math
|
import math
|
||||||
import strformat
|
import strformat
|
||||||
|
|
||||||
|
|
||||||
proc natPrint(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
|
|
||||||
|
proc natPrint*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||||
## Native function print
|
## Native function print
|
||||||
## Prints an object representation
|
## Prints an object representation
|
||||||
## to stdout. If more than one argument
|
## to stdout. If more than one argument
|
||||||
|
@ -44,10 +44,10 @@ proc natPrint(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
|
||||||
# Note: we return nil and not asNil() because
|
# Note: we return nil and not asNil() because
|
||||||
# the VM will later use its own cached pointer
|
# the VM will later use its own cached pointer
|
||||||
# to nil
|
# to nil
|
||||||
return (ok: true, result: nil)
|
return (kind: retNative.Nil, result: nil)
|
||||||
|
|
||||||
|
|
||||||
proc natClock(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
|
proc natClock*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||||
## Native function clock
|
## Native function clock
|
||||||
## Returns the current unix
|
## Returns the current unix
|
||||||
## time (also known as epoch)
|
## time (also known as epoch)
|
||||||
|
@ -55,10 +55,10 @@ proc natClock(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
|
||||||
|
|
||||||
# TODO: Move this to a separate module once we have imports
|
# TODO: Move this to a separate module once we have imports
|
||||||
|
|
||||||
result = (ok: true, result: getTime().toUnixFloat().asFloat())
|
result = (kind: retNative.Object, result: getTime().toUnixFloat().asFloat())
|
||||||
|
|
||||||
|
|
||||||
proc natRound(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
|
proc natRound*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||||
## Rounds a floating point number to a given
|
## Rounds a floating point number to a given
|
||||||
## precision (when precision == 0, this function drops the
|
## precision (when precision == 0, this function drops the
|
||||||
## decimal part and returns an integer). Note that when
|
## decimal part and returns an integer). Note that when
|
||||||
|
@ -68,53 +68,39 @@ proc natRound(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
|
||||||
var precision = 0
|
var precision = 0
|
||||||
if len(args) notin 1..2:
|
if len(args) notin 1..2:
|
||||||
# Here we need to return immediately to exit the procedure
|
# Here we need to return immediately to exit the procedure
|
||||||
return (ok: false, result: newTypeError(&"function 'round' takes from 1 to 2 arguments, got {len(args)}"))
|
return (kind: retNative.Exception, result: newTypeError(&"function 'round' takes from 1 to 2 arguments, got {len(args)}"))
|
||||||
elif len(args) == 2:
|
elif len(args) == 2:
|
||||||
if not args[1].isInt():
|
if not args[1].isInt():
|
||||||
return (ok: false, result: newTypeError(&"precision must be of type 'int', not '{args[1].typeName()}'"))
|
return (kind: retNative.Exception, result: newTypeError(&"precision must be of type 'int', not '{args[1].typeName()}'"))
|
||||||
else:
|
else:
|
||||||
precision = args[1].toInt()
|
precision = args[1].toInt()
|
||||||
if args[0].kind notin {ObjectType.Integer, ObjectType.Float}:
|
if args[0].kind notin {ObjectType.Integer, ObjectType.Float}:
|
||||||
return (ok: false, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
|
return (kind: retNative.Exception, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
|
||||||
if precision < 0:
|
if precision < 0:
|
||||||
result = (ok: false, result: newTypeError(&"precision must be positive"))
|
result = (kind: retNative.Exception, result: newTypeError(&"precision must be positive"))
|
||||||
else:
|
else:
|
||||||
if args[0].isInt():
|
if args[0].isInt():
|
||||||
result = (ok: true, result: args[0])
|
result = (kind: retNative.Object, result: args[0])
|
||||||
elif precision == 0:
|
elif precision == 0:
|
||||||
result = (ok: true, result: int(args[0].toFloat()).asInt())
|
result = (kind: retNative.Object, result: int(args[0].toFloat()).asInt())
|
||||||
else:
|
else:
|
||||||
result = (ok: true, result: round(args[0].toFloat(), precision).asFloat())
|
result = (kind: retNative.Object, result: round(args[0].toFloat(), precision).asFloat())
|
||||||
|
|
||||||
|
|
||||||
proc natToInt(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
|
proc natToInt*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||||
## Drops the decimal part of a float and returns an integer.
|
## Drops the decimal part of a float and returns an integer.
|
||||||
## If the value is already an integer, the same object is returned
|
## If the value is already an integer, the same object is returned
|
||||||
if args[0].isInt():
|
if args[0].isInt():
|
||||||
result = (ok: true, result: args[0])
|
result = (kind: retNative.Object, result: args[0])
|
||||||
elif args[0].isFloat():
|
elif args[0].isFloat():
|
||||||
result = (ok: true, result: int(args[0].toFloat()).asInt())
|
result = (kind: retNative.Object, result: int(args[0].toFloat()).asInt())
|
||||||
else:
|
else:
|
||||||
result = (ok: false, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
|
result = (kind: retNative.Exception, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
|
||||||
|
|
||||||
|
|
||||||
proc natType(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
|
proc natType*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||||
## Returns the type of a given object as a string
|
## Returns the type of a given object as a string
|
||||||
result = (ok: true, result: args[0].typeName().asStr())
|
result = (kind: retNative.Object, result: args[0].typeName().asStr())
|
||||||
|
|
||||||
|
|
||||||
proc stdlibInit*(vm: VM) =
|
|
||||||
## Initializes the VM's standard library by defining builtin
|
|
||||||
## functions that do not require imports. An arity of -1
|
|
||||||
## means that the function is variadic (or that it can
|
|
||||||
## take a different number of arguments according to
|
|
||||||
## how it's called) and should be handled by the nim
|
|
||||||
## procedure accordingly
|
|
||||||
vm.defineGlobal("print", newNative("print", natPrint, -1))
|
|
||||||
vm.defineGlobal("clock", newNative("clock", natClock, 0))
|
|
||||||
vm.defineGlobal("round", newNative("round", natRound, -1))
|
|
||||||
vm.defineGlobal("toInt", newNative("toInt", natToInt, 1))
|
|
||||||
vm.defineGlobal("type", newNative("type", natType, 1))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -59,5 +59,4 @@ proc newObj*(): ptr Obj =
|
||||||
proc asObj*(self: ptr Obj): ptr Obj =
|
proc asObj*(self: ptr Obj): ptr Obj =
|
||||||
## Casts a specific JAPL object into a generic
|
## Casts a specific JAPL object into a generic
|
||||||
## pointer to Obj
|
## pointer to Obj
|
||||||
|
|
||||||
result = cast[ptr Obj](self)
|
result = cast[ptr Obj](self)
|
||||||
|
|
|
@ -18,6 +18,22 @@ import baseObject
|
||||||
import japlString
|
import japlString
|
||||||
|
|
||||||
|
|
||||||
|
type retNative* {.pure.} = enum
|
||||||
|
## Defines all the possible native
|
||||||
|
## return types (this is useful
|
||||||
|
## to keep singletons actually
|
||||||
|
## singletons and makes it easier
|
||||||
|
## to bring back exceptions as well)
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
Inf,
|
||||||
|
nInf,
|
||||||
|
Nil,
|
||||||
|
NotANumber,
|
||||||
|
Object,
|
||||||
|
Exception
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
Native* = object of Obj
|
Native* = object of Obj
|
||||||
## A native object
|
## A native object
|
||||||
|
@ -25,10 +41,10 @@ type
|
||||||
arity*: int # The number of required parameters
|
arity*: int # The number of required parameters
|
||||||
optionals*: int # The number of optional parameters
|
optionals*: int # The number of optional parameters
|
||||||
defaults*: seq[ptr Obj] # List of default arguments, in order
|
defaults*: seq[ptr Obj] # List of default arguments, in order
|
||||||
nimproc*: proc (args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] # The function's body
|
nimproc*: proc (args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] # The function's body
|
||||||
|
|
||||||
|
|
||||||
proc newNative*(name: string, nimproc: proc(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj], arity: int = 0): ptr Native =
|
proc newNative*(name: string, nimproc: proc(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj], arity: int = 0): ptr Native =
|
||||||
## Allocates a new native object with the given
|
## Allocates a new native object with the given
|
||||||
## bytecode chunk and arity. If the name is an empty string
|
## bytecode chunk and arity. If the name is an empty string
|
||||||
## (the default), the function will be an
|
## (the default), the function will be an
|
||||||
|
|
75
src/vm.nim
75
src/vm.nim
|
@ -22,6 +22,7 @@ import strformat
|
||||||
import tables
|
import tables
|
||||||
import std/enumerate
|
import std/enumerate
|
||||||
## Our modules
|
## Our modules
|
||||||
|
import stdlib
|
||||||
import memory
|
import memory
|
||||||
import config
|
import config
|
||||||
import compiler
|
import compiler
|
||||||
|
@ -59,7 +60,7 @@ type
|
||||||
stackTop*: int
|
stackTop*: int
|
||||||
objects*: seq[ptr Obj]
|
objects*: seq[ptr Obj]
|
||||||
globals*: Table[string, ptr Obj]
|
globals*: Table[string, ptr Obj]
|
||||||
cached: array[5, ptr Obj]
|
cached: array[6, ptr Obj]
|
||||||
file*: string
|
file*: string
|
||||||
|
|
||||||
|
|
||||||
|
@ -243,21 +244,27 @@ proc call(self: VM, native: ptr Native, argCount: int): bool =
|
||||||
for i in countup(slot, self.stack.high()):
|
for i in countup(slot, self.stack.high()):
|
||||||
args.add(self.stack[i])
|
args.add(self.stack[i])
|
||||||
let nativeResult = native.nimproc(args)
|
let nativeResult = native.nimproc(args)
|
||||||
if not nativeResult.ok:
|
|
||||||
self.error(cast[ptr JaplException](nativeResult.result))
|
|
||||||
return false
|
|
||||||
# assumes that all native procs behave well, and if not ok, they
|
|
||||||
# only return japl exceptions
|
|
||||||
for i in countup(slot - 1, self.stack.high()):
|
for i in countup(slot - 1, self.stack.high()):
|
||||||
discard self.pop() # TODO once stack is a custom datatype,
|
discard self.pop() # TODO once stack is a custom datatype,
|
||||||
# just reduce its length
|
# just reduce its length
|
||||||
if nativeResult.result == nil:
|
case nativeResult.kind:
|
||||||
# Since nil is a singleton, natives
|
of retNative.True:
|
||||||
# don't reallocate it and we need to
|
self.push(self.getBoolean(true))
|
||||||
# reuse our cached object instead
|
of retNative.False:
|
||||||
self.push(self.cached[2])
|
self.push(self.getBoolean(false))
|
||||||
else:
|
of retNative.Object:
|
||||||
self.push(nativeResult.result)
|
self.push(nativeResult.result)
|
||||||
|
of retNative.Nil:
|
||||||
|
self.push(self.cached[2])
|
||||||
|
of retNative.Inf:
|
||||||
|
self.push(self.cached[3])
|
||||||
|
of retNative.nInf:
|
||||||
|
self.push(self.cached[4])
|
||||||
|
of retNative.NotANumber:
|
||||||
|
self.push(self.cached[5])
|
||||||
|
of retNative.Exception:
|
||||||
|
self.error(cast[ptr JaplException](nativeResult.result))
|
||||||
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
@ -322,30 +329,25 @@ proc run(self: VM, repl: bool): InterpretResult =
|
||||||
## them one at a time: this is the runtime's
|
## them one at a time: this is the runtime's
|
||||||
## main loop
|
## main loop
|
||||||
var frame = self.frames[self.frameCount - 1]
|
var frame = self.frames[self.frameCount - 1]
|
||||||
var instruction: uint8
|
|
||||||
var opcode: OpCode
|
var opcode: OpCode
|
||||||
|
when DEBUG_TRACE_VM:
|
||||||
|
var iteration: int = 0
|
||||||
while true:
|
while true:
|
||||||
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
|
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
|
||||||
instruction = frame.readByte()
|
opcode = OpCode(frame.readByte())
|
||||||
opcode = OpCode(instruction)
|
|
||||||
when DEBUG_TRACE_VM: # Insight inside the VM
|
when DEBUG_TRACE_VM: # Insight inside the VM
|
||||||
stdout.write("Current VM stack status: [")
|
iteration += 1
|
||||||
|
stdout.write(&"Iteration N. {iteration}\nCurrent VM stack status: [")
|
||||||
for i, v in self.stack:
|
for i, v in self.stack:
|
||||||
stdout.write(stringify(v))
|
stdout.write(stringify(v))
|
||||||
if i < self.stack.high():
|
if i < self.stack.high():
|
||||||
stdout.write(", ")
|
stdout.write(", ")
|
||||||
stdout.write("]\n")
|
stdout.write("]\nCurrent global scope status: {")
|
||||||
stdout.write("Current global scope status: {")
|
|
||||||
for i, (k, v) in enumerate(self.globals.pairs()):
|
for i, (k, v) in enumerate(self.globals.pairs()):
|
||||||
stdout.write("'")
|
stdout.write(&"'{k}': {stringify(v)}")
|
||||||
stdout.write(k)
|
|
||||||
stdout.write("'")
|
|
||||||
stdout.write(": ")
|
|
||||||
stdout.write(stringify(v))
|
|
||||||
if i < self.globals.len() - 1:
|
if i < self.globals.len() - 1:
|
||||||
stdout.write(", ")
|
stdout.write(", ")
|
||||||
stdout.write("}\n")
|
stdout.write("}\nCurrent frame type: ")
|
||||||
stdout.write("Current frame type: ")
|
|
||||||
if frame.function.name == nil:
|
if frame.function.name == nil:
|
||||||
stdout.write("main\n")
|
stdout.write("main\n")
|
||||||
else:
|
else:
|
||||||
|
@ -358,8 +360,7 @@ proc run(self: VM, repl: bool): InterpretResult =
|
||||||
stdout.write(stringify(e))
|
stdout.write(stringify(e))
|
||||||
if i < frame.function.chunk.consts.high():
|
if i < frame.function.chunk.consts.high():
|
||||||
stdout.write(", ")
|
stdout.write(", ")
|
||||||
stdout.write("]\n")
|
stdout.write("]\nCurrent frame stack status: ")
|
||||||
stdout.write("Current frame stack status: ")
|
|
||||||
stdout.write("[")
|
stdout.write("[")
|
||||||
for i, e in frame.getView():
|
for i, e in frame.getView():
|
||||||
stdout.write(stringify(e))
|
stdout.write(stringify(e))
|
||||||
|
@ -704,8 +705,27 @@ proc initCache(self: VM) =
|
||||||
false.asBool().asObj(),
|
false.asBool().asObj(),
|
||||||
asNil().asObj(),
|
asNil().asObj(),
|
||||||
asInf().asObj(),
|
asInf().asObj(),
|
||||||
|
nil,
|
||||||
asNan().asObj()
|
asNan().asObj()
|
||||||
]
|
]
|
||||||
|
# We cache -inf as well
|
||||||
|
let nInf = asInf()
|
||||||
|
nInf.isNegative = true
|
||||||
|
self.cached[4] = nInf.asObj()
|
||||||
|
|
||||||
|
|
||||||
|
proc stdlibInit*(vm: VM) =
|
||||||
|
## Initializes the VM's standard library by defining builtin
|
||||||
|
## functions that do not require imports. An arity of -1
|
||||||
|
## means that the function is variadic (or that it can
|
||||||
|
## take a different number of arguments according to
|
||||||
|
## how it's called) and should be handled by the nim
|
||||||
|
## procedure accordingly
|
||||||
|
vm.defineGlobal("print", newNative("print", natPrint, -1))
|
||||||
|
vm.defineGlobal("clock", newNative("clock", natClock, 0))
|
||||||
|
vm.defineGlobal("round", newNative("round", natRound, -1))
|
||||||
|
vm.defineGlobal("toInt", newNative("toInt", natToInt, 1))
|
||||||
|
vm.defineGlobal("type", newNative("type", natType, 1))
|
||||||
|
|
||||||
|
|
||||||
proc initVM*(): VM =
|
proc initVM*(): VM =
|
||||||
|
@ -714,6 +734,7 @@ proc initVM*(): VM =
|
||||||
var globals: Table[string, ptr Obj] = initTable[string, ptr Obj]()
|
var globals: Table[string, ptr Obj] = initTable[string, ptr Obj]()
|
||||||
result = VM(lastPop: asNil(), objects: @[], globals: globals, source: "", file: "")
|
result = VM(lastPop: asNil(), objects: @[], globals: globals, source: "", file: "")
|
||||||
result.initCache()
|
result.initCache()
|
||||||
|
result.stdlibInit()
|
||||||
|
|
||||||
|
|
||||||
proc interpret*(self: VM, source: string, repl: bool = false, file: string): InterpretResult =
|
proc interpret*(self: VM, source: string, repl: bool = false, file: string): InterpretResult =
|
||||||
|
|
|
@ -15,7 +15,7 @@ print(1 or 2 or 3 or 4);//output:1
|
||||||
print(1 and 2 and 3 and 4);//output:4
|
print(1 and 2 and 3 and 4);//output:4
|
||||||
print(1 and 2 or 3 and 4);//output:2
|
print(1 and 2 or 3 and 4);//output:2
|
||||||
print(1 and false or 3 and 4);//output:4
|
print(1 and false or 3 and 4);//output:4
|
||||||
print(!0);//output:true
|
print(not 0);//output:true
|
||||||
print(!1);//output:false
|
print(not 1);//output:false
|
||||||
print(!1 and !2);//output:false
|
print(not 1 and not 2);//output:false
|
||||||
print(!(1 and 0));//output:true
|
print(not (1 and 0));//output:true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var x = 4;
|
var x = 4;
|
||||||
var y = x;
|
var y = x;
|
||||||
|
|
||||||
print(x is y);//output:false
|
print(x is y);//output:true
|
||||||
print(x is x);//output:true
|
print(x is x);//output:true
|
||||||
print(x is 4);//output:false
|
print(x is 4);//output:false
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue