https://toitlang.org/ logo
Is there a way to build / flash, while ignoring co...
# help
a
I c++ I can do something like
Copy code
#ifdef FOO_BAR
    doStuff();
#endif
Is there a similar way to do this sort of thing with toit? The primary usecase for me would be to entirely avoid building verbose log lines, which in their call might create long strings etc, when there is no expectaiton for that log line to every be used? The only alternative right now I guess would be commenting and un commenting lines continually?
f
If you use
if SOME-GLOBAL-CONSTANT:
then the compiler will remove the dead code if it can see that the constant is false.
Another approach is sometimes to make the string construction be block based. For example:
Copy code
my-log [block]:
  if SHOULD-LOG: print block.call

SHOULD-LOG ::= true

main:
  my-log: "$(expensive) construction of some long string"
This would still do a call and an
if
, but the expensive contruction would not be done (if
SHOULD-LOG
is set to false).
a
thats quite neat,and could allow me to define my own log levels?
Copy code
LEVEL-DEBUG ::= 4
LEVEL-TRACE ::= 5
LOG-LEVEL ::= LEVEL-DEBUG

do-log-debug [block]:
  if LOG-LEVEL >= LEVEL-DEBUG: print block.call

do-log-trace [block]:
  if LOG-LEVEL >= LEVEL-TRACE: print block.call

main:
  do-log-debug: "$(expensive) construction of some long debug string"
  do-log-trace: "$(expensive) construction of some long trace string"
Or would this not get optimized down during compilation?
f
Simple expressions are also optimized away.
But I will check now 🙂
Copy code
ᐅ toit tools snapshot bytecodes /tmp/out.snapshot do-log-
Bytecodes for methods[2]: (only printing elements matching: "do-log-")

259: do-log-debug /home/flo/tmp/cp.toit:5:1
  0/ 263 [016] - load local 2
  1/ 264 [055] - invoke block S1
  3/ 266 [053] - invoke static print <sdk>/core/print.toit:13:1
  6/ 269 [090] - return null S1 1

272: do-log-trace /home/flo/tmp/cp.toit:8:1
  0/ 276 [090] - return null S0 1
a
So thats a yes? 😉
f
Yes 🙂
a
So, the answer is probably that I should go and try and run this bytecode snapshot tool myself 😄 But i wonder if sometihng like this would still work in terms of keeping things light at complication time? (Still a simple expression, but its only evaulated in a class instance)
Copy code
import lite-logging

log ::= lite-logging.new-default lite-logging.DEBUG-LEVEL

main:
  log.debug: "Hello, world!"
and
Copy code
import log
import .levels

new-default level/int -> Logger:
  return Logger log.default level

class Logger:
  
  logger /log.Logger
  level /int

  constructor .logger/log.Logger .level/int:
    logger = logger
    level = level

  debug [block]:
    if level >= DEBUG-LEVEL: print block.call
f
You mean at runtime?
Here you don't have static constants anymore so there will be a check
a
I did try something like
Copy code
import lite-logging

LITE-LOG-LEVEL ::= lite-logging.DEBUG-LEVEL
log ::= lite-logging.new-default

main:
  log.debug: "Hello, world!"
first, but
LITE-LOG-LEVEL
is not accessible in the class
But basically I was aiming for something there toy could define the log level at the top of your own code, but take advantage of a little package to implement the block style logging methods
f
That's not really possible.
If it's just a boolean the type inference might realize that a type is
True
or
False
even if it's inside a class, but you would still have the problem, that these types are shared among all users of the logger.
You could, however, have different loggers.
So it would then be
log ::= lite-logging.DebugLogger
. But you don't need any constant-folding for that.
a
ooo, different loggers, with a null logger approach might work quite nicely
f
You could also just pass the log-level as argument to the Logger and then dispatch (factory constructor) in the logger.
a
As a version one, this is a slightly nice wrapper for my first use
Copy code
import lite-logging
log ::= lite-logging.new lite-logging.DEBUG-LEVEL

main:
  log.debug: "Hello, world!"
and
Copy code
import log as toit-log
import .levels

new level/int --name/string?=null-> Logger:
  return Logger level --name=name

class Logger:
  
  logger /toit-log.Logger
  logger-level /int

  constructor .logger-level/int --name/string?=null:
    logger = toit-log.Logger (max toit-log.DEBUG-LEVEL logger-level) toit-log.DefaultTarget --name=name
    logger-level = logger-level

  log level/int [block] --tags/Map?=null -> none:
    if logger-level <= level: logger.log level block.call --tags=tags

  trace [block] --tags/Map?=null -> none:
    // Uses the debug level logger, as the toit logger has no trace level..
    if logger-level <= TRACE-LEVEL: logger.log DEBUG-LEVEL "TRACE: $(block.call)" --tags=tags

  debug [block] --tags/Map?=null -> none:
    if logger-level <= DEBUG-LEVEL: logger.log DEBUG-LEVEL block.call --tags=tags
  
  info [block] --tags/Map?=null -> none:
    if logger-level <= INFO-LEVEL: logger.log INFO-LEVEL block.call --tags=tags
  
  warn [block] --tags/Map?=null -> none:
    if logger-level <= WARN-LEVEL: logger.log WARN-LEVEL block.call --tags=tags
  
  error [block] --tags/Map?=null -> none:
    if logger-level <= ERROR-LEVEL: logger.log ERROR-LEVEL block.call --tags=tags
Which means I can use blocks to avoid possibly expansive strings for debug logging, and leave the logging in place when the app is run in different logging modes. It doesnt compile away the stuff that won't be used though. but solves my main pain, and doesnt look that bad at all
f
If the level of the logger can't be changed, you could have subclasses that don't have the
if
.
Not sure it's worth it.
More loggers also mean that the method dispatches become more expensive.
a
I'll try running with this for a bit, and see if I regret all of this in a few weeks 😉