<gh_stars>100-1000
#!/bin/false

# This file is part of Espruino, a JavaScript interpreter for Microcontrollers
#
# Copyright (C) 2013 <NAME> <<EMAIL>>
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# ----------------------------------------------------------------------------------------
# Reads board information from boards/BOARDNAME.py - used by build_board_docs,
# build_pininfo, and build_platform_config
# ----------------------------------------------------------------------------------------

# Global
import subprocess;
import re;
import json;
import sys;
import os;
import importlib;
# Local
import pinutils;

silent = os.getenv("SILENT");
if silent:
  class Discarder(object):
    def write(self, text):
        pass # do nothing
    def flush(self):
        pass # do nothing
  # now discard everything coming out of stdout
  sys.stdout = Discarder()

# http://stackoverflow.com/questions/4814970/subprocess-check-output-doesnt-seem-to-exist-python-2-6-5
if "check_output" not in dir( subprocess ):
    def f(*popenargs, **kwargs):
        if 'stdout' in kwargs:
            raise ValueError('stdout argument not allowed, it will be overridden.')
        process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
        output, unused_err = process.communicate()
        retcode = process.poll()
        if retcode:
            cmd = kwargs.get("args")
            if cmd is None:
                cmd = popenargs[0]
            raise subprocess.CalledProcessError(retcode, cmd)
        return output
    subprocess.check_output = f


# Scans files for comments of the form /*JSON......*/
#
# Comments look like:
#
#/*JSON{ "type":"staticmethod|staticproperty|constructor|method|property|function|variable|class|library|idle|init|kill|EV_xxx",
#                      // class = built-in class that does not require instantiation
#                      // library = built-in class that needs require('classname')
#                      // idle = function to run on idle regardless
#                      // init = function to run on initialisation
#                      // kill = function to run on deinitialisation
#                      // EV_xxx = Something to be called with a character in an IRQ when it is received (eg. EV_SERIAL1)
#         "class" : "Double", "name" : "doubleToIntBits",
#         "needs_parentName":true,           // optional - if for a method, this makes the first 2 args parent+parentName (not just parent)
#         "generate_full|generate|wrap" : "*(JsVarInt*)&x", // if generate=false, it'll only be used for docs
#         "generate_js" : "full/file/path.js", // you can supply a JS file instead of 'generate' above. Should be of the form '(function(args) { ... })'
#         "description" : " Convert the floating point value given into an integer representing the bits contained in it",
#         "params" : [ [ "x" , "float|int|int32|bool|pin|JsVar|JsVarName|JsVarArray", "A floating point number"] ],
#                               // float - parses into a JsVarFloat which is passed to the function
#                               // int - parses into a JsVarInt which is passed to the function
#                               // int32 - parses into a 32 bit int
#                               // bool - parses into a boolean
#                               // pin - parses into a pin
#                               // JsVar - passes a JsVar* to the function (after skipping names)
#                               // JsVarArray - parses this AND ANY SUBSEQUENT ARGUMENTS into a JsVar of type JSV_ARRAY. THIS IS ALWAYS DEFINED, EVEN IF ZERO LENGTH. Currently it must be the only parameter
#         "return" : ["int|float|JsVar", "The integer representation of x"],
#         "return_object" : "ObjectName", // optional - used for tern's code analysis - so for example we can do hints for openFile(...).yyy
#         "no_create_links":1                // optional - if this is set then hyperlinks are not created when this name is mentioned (good example = bit() )
#         "not_real_object" : "anything",    // optional - for classes, this means we shouldn't treat this as a built-in object, as internally it isn't stored in a JSV_OBJECT
#         "prototype" : "Object",    // optional - for classes, this is what their prototype is. It's particlarly helpful if not_real_object, because there is no prototype var in that case
#         "check" : "jsvIsFoo(var)", // for classes - this is code that returns true if 'var' is of the given type
#         "ifndef" : "SAVE_ON_FLASH", // if the given preprocessor macro is defined, don't implement this
#         "ifdef" : "USE_LCD_FOO", // if the given preprocessor macro isn't defined, don't implement this
#         "#if" : "A>2", // add a #if statement in the generated C file (ONLY if type==object)
#}*/
#
# description can be an array of strings as well as a simple string (in which case each element is separated by a newline),
# and adding ```sometext``` in the description surrounds it with HTML code tags
#
# COMMAND LINE OPTIONS
# -Ddefinition
# -BBOARDFILE

def get_jsondata(is_for_document, parseArgs = True, board = False):
    scriptdir = os.path.dirname	(os.path.realpath(__file__))
    print("Script location "+scriptdir)
    os.chdir(scriptdir+"/..")

    ignore_ifdefs = is_for_document

    # C files that we'll scan for JSON data
    jswraps = []
    # definitions that are used when evaluating IFDEFs/etc
    defines = []

    explicit_files = False
    if parseArgs and len(sys.argv)>1:
      print("Using files from command line")
      for i in range(1,len(sys.argv)):
        arg = sys.argv[i]
        if arg[0]=="-":
          if arg[1]=="D":
            defines.append(arg[2:])
          elif arg[1]=="B":
            print("BOARD "+arg[2:]);
            print("Now ignore_ifdefs = False");
            ignore_ifdefs = False
            board = importlib.import_module(arg[2:])            
          elif arg[1]=="F":
            "" # -Fxxx.yy in args is filename xxx.yy, which is mandatory for build_jswrapper.py
          else:
            print("Unknown command-line option")
            exit(1)
        elif arg[-2:]==".c": 
          # C file, all good
          explicit_files = True
          jswraps.append(arg)
        else:
          print("WARNING: Ignoring unknown file type: " + arg)
    if not explicit_files:
      print("Scanning for jswrap.c files")
      jswraps = subprocess.check_output(["find", ".", "-name", "jswrap*.c"]).strip().split("\n")

    if board:
      if "usart" in board.chip: defines.append("USART_COUNT="+str(board.chip["usart"]));
      if "spi" in board.chip: defines.append("SPI_COUNT="+str(board.chip["spi"]));
      if "i2c" in board.chip: defines.append("I2C_COUNT="+str(board.chip["i2c"]));
      if "USB" in board.devices: defines.append("defined(USB)=True");
      else: defines.append("defined(USB)=False");
      if "build" in board.info:
        if "defines" in board.info["build"]:
          for i in board.info["build"]["defines"]:
            print("board.defines: " + i);
            defines.append(i)
        if "makefile" in board.info["build"]:
          for i in board.info["build"]["makefile"]:           
            print("board.makefile: " + i);
            i = i.strip()
            if i.startswith("DEFINES"): 
              defs = i[7:].strip()[2:].strip().split() # array of -Dsomething
              for d in defs: 
                if not d.startswith("-D"):
                  print("WARNING: expecting -Ddefine, got " + d)
                defines.append(d[2:])

    if len(defines)>1:
      print("Got #DEFINES:")
      for d in defines: print("   "+d)

    jsondatas = []
    for jswrap in jswraps:
      # ignore anything from archives
      if jswrap.startswith("./archives/"): continue

      # now scan
      print("Scanning "+jswrap)
      code = open(jswrap, "r").read()

      if is_for_document and not explicit_files and "DO_NOT_INCLUDE_IN_DOCS" in code:
        print("FOUND 'DO_NOT_INCLUDE_IN_DOCS' IN FILE "+jswrap)
        continue

      for comment in re.findall(r"/\*JSON.*?\*/", code, re.VERBOSE | re.MULTILINE | re.DOTALL):
        charnumber = code.find(comment)
        linenumber = 1+code.count("\n", 0, charnumber)
        # Strip off /*JSON .. */ bit
        comment = comment[6:-2]

        endOfJson = comment.find("\n}")+2;
        jsonstring = comment[0:endOfJson];
        description =  comment[endOfJson:].strip();
#            print("Parsing "+jsonstring)
        try:
          jsondata = json.loads(jsonstring)
          if len(description): jsondata["description"] = description;
          jsondata["filename"] = jswrap
          if jswrap[-2:]==".c":
            jsondata["include"] = jswrap[:-2]+".h"
          jsondata["githublink"] = "https://github.com/espruino/Espruino/blob/master/"+jswrap+"#L"+str(linenumber)

          dropped_prefix = "Dropped "
          if "name" in jsondata: dropped_prefix += jsondata["name"]+" "
          elif "class" in jsondata: dropped_prefix += jsondata["class"]+" "
          drop = False
          if not ignore_ifdefs:
            if ("generate" in jsondata) and jsondata["generate"]==False and not is_for_document:
              print(dropped_prefix+" because of generate=false")
              drop = True
            if ("ifndef" in jsondata) and (jsondata["ifndef"] in defines):
              print(dropped_prefix+" because of #ifndef "+jsondata["ifndef"])
              drop = True
            if ("ifdef" in jsondata) and not (jsondata["ifdef"] in defines):
              print(dropped_prefix+" because of #ifdef "+jsondata["ifdef"])
              drop = True
            if ("#ifdef" in jsondata) or ("#ifndef" in jsondata):
              sys.stderr.write( "'#ifdef' where 'ifdef' should be used in " + jsonstring + " - "+str(sys.exc_info()[0]) + "\n" )
              exit(1)
            if ("if" in jsondata):
              sys.stderr.write( "'if' where '#if' should be used in " + jsonstring + " - "+str(sys.exc_info()[0]) + "\n" )
              exit(1)
            if ("#if" in jsondata):
              expr = jsondata["#if"]
              for defn in defines:
                expr = expr.replace("defined("+defn+")", "True");
                if defn.find('=')!=-1:
                  dname = defn[:defn.find('=')]
                  dkey = defn[defn.find('=')+1:]
                  expr = expr.replace("defined("+dname+")", "True");
                  expr = expr.replace(dname, dkey);
              # Now replace any defined(...) we haven't heard of with false
              expr = re.sub(r"defined\([^\)]*\)", "False", expr)
              expr = expr.replace("||","or").replace("&&","and");
              expr = expr.replace("!","not ");
              try:
                r = eval(expr)
              except:
                print("WARNING: error evaluating '"+expr+"' - from '"+jsondata["#if"]+"'")
                r = True
              if not r:
                print(dropped_prefix+" because of #if "+jsondata["#if"]+ " -> "+expr)
                drop = True
          if not drop:
            jsondatas.append(jsondata)
        except ValueError as e:
          sys.stderr.write( "JSON PARSE FAILED for " +  jsonstring + " - "+ str(e) + "\n")
          exit(1)
        except:
          sys.stderr.write( "JSON PARSE FAILED for " + jsonstring + " - "+str(sys.exc_info()[0]) + "\n" )
          exit(1)
    print("Scanning finished.")

    if board:
      for device in pinutils.SIMPLE_DEVICES:
        if device in board.devices:
          jsondatas.append({
            "type" : "variable",
            "name" : device,
            "generate_full" : device+"_PININDEX",
            "return" : ["pin", device],
            "filename" : "BOARD.py",
            "include" : "platform_config.h"
          })
      if "LED1" in board.devices:
        jsondatas.append({
          "type" : "variable",
          "name" : "LED",
          "generate_full" : "LED1_PININDEX",
          "return" : ["pin", "LED1"],
          "filename" : "BOARD.py",
          "include" : "platform_config.h"
        })
      if "BTN1" in board.devices:
        jsondatas.append({
          "type" : "variable",
          "name" : "BTN",
          "generate_full" : "BTN1_PININDEX",
          "return" : ["pin", "Button 1"],
          "filename" : "BOARD.py",
          "include" : "platform_config.h"
        })
    return jsondatas

# Takes the data from get_jsondata and restructures it in prepartion for output as JS
#
# Results look like:,
#{
#  "Pin": {
#    "desc": [
#      "This is the built-in class for Pins, such as D0,D1,LED1, or BTN",
#      "You can call the methods on Pin, or you can use Wiring-style functions such as digitalWrite"
#    ],
#    "methods": {
#      "read": {
#        "desc": "Returns the input state of the pin as a boolean",
#        "params": [],
#        "return": [
#          "bool",
#          "Whether pin is a logical 1 or 0"
#        ]
#      },
#      "reset": {
#        "desc": "Sets the output state of the pin to a 0",
#        "params": [],
#        "return": []
#      },
#      ...
#    },
#    "props": {},
#    "staticmethods": {},
#    "staticprops": {}
#  },
#  "print": {
#    "desc": "Print the supplied string",
#    "return": []
#  },
#  ...
#}
#

def get_struct_from_jsondata(jsondata):
  context = {"modules": {}}

  def checkClass(details):
    cl = details["class"]
    if not cl in context:
      context[cl] = {"type": "class", "methods": {}, "props": {}, "staticmethods": {}, "staticprops": {}, "desc": details.get("description", "")}
    return cl

  def addConstructor(details):
    cl = checkClass(details)
    context[cl]["constructor"] = {"params": details.get("params", []), "return": details.get("return", []), "desc": details.get("description", "")}

  def addMethod(details, type = ""):
    cl = checkClass(details)
    context[cl][type + "methods"][details["name"]] = {"params": details.get("params", []), "return": details.get("return", []), "desc": details.get("description", "")}

  def addProp(details, type = ""):
    cl = checkClass(details)
    context[cl][type + "props"][details["name"]] = {"return": details.get("return", []), "desc": details.get("description", "")}

  def addFunc(details):
    context[details["name"]] = {"type": "function", "return": details.get("return", []), "desc": details.get("description", "")}

  def addObj(details):
    context[details["name"]] = {"type": "object", "instanceof": details.get("instanceof", ""), "desc": details.get("description", "")}

  def addLib(details):
    context["modules"][details["class"]] = {"desc": details.get("description", "")}
  def addVar(details):
    return

  for data in jsondata:
    type = data["type"]
    if type=="class":
      checkClass(data)
    elif type=="constructor":
      addConstructor(data)
    elif type=="method":
      addMethod(data)
    elif type=="property":
      addProp(data)
    elif type=="staticmethod":
      addMethod(data, "static")
    elif type=="staticproperty":
      addProp(data, "static")
    elif type=="function":
      addFunc(data)
    elif type=="object":
      addObj(data)
    elif type=="library":
      addLib(data)
    elif type=="variable":
      addVar(data)
    else:
      print(json.dumps(data, sort_keys=True, indent=2))

  return context

def get_includes_from_jsondata(jsondatas):
        includes = []
        for jsondata in jsondatas:
          if "include" in jsondata:
            include = jsondata["include"]
            if not include in includes:
              includes.append(include)
        return includes

def is_property(jsondata):
  return jsondata["type"]=="property" or jsondata["type"]=="staticproperty" or jsondata["type"]=="variable"

def is_function(jsondata):
  return jsondata["type"]=="function" or jsondata["type"]=="method"

def get_prefix_name(jsondata):
  if jsondata["type"]=="event": return "event"
  if jsondata["type"]=="constructor": return "constructor"
  if jsondata["type"]=="function": return "function"
  if jsondata["type"]=="method": return "function"
  if jsondata["type"]=="variable": return "variable"
  if jsondata["type"]=="property": return "property"
  return ""

def get_ifdef_description(d):
  if d=="SAVE_ON_FLASH": return "devices with low flash memory"
  if d=="SAVE_ON_FLASH_EXTREME": return "devices with extremely low flash memory (eg. HYSTM32_28)"
  if d=="STM32": return "STM32 devices (including Espruino Original, Pico and WiFi)"
  if d=="STM32F1": return "STM32F1 devices (including Original Espruino Board)"
  if d=="NRF52": return "NRF52 devices (like Puck.js, Pixl.js and MDBT42Q)"
  if d=="PUCKJS": return "Puck.js devices"
  if d=="PIXLJS": return "Pixl.js boards"
  if d=="ESPRUINOWIFI": return "Espruino WiFi boards"
  if d=="ESPRUINOBOARD": return "'Original' Espruino boards"
  if d=="PICO": return "Espruino Pico boards"
  if d=="BANGLEJS": return "Bangle.js smartwatches"
  if d=="ESP8266": return "ESP8266 boards running Espruino"
  if d=="ESP32": return "ESP32 boards"
  if d=="EFM32": return "EFM32 devices"
  if d=="USE_LCD_SDL": return "Linux with SDL support compiled in"
  if d=="USE_TLS": return "devices with TLS and SSL support (Espruino Pico and Espruino WiFi only)"
  if d=="RELEASE": return "release builds"
  if d=="DEBUG": return "debug builds"
  if d=="LINUX": return "Linux-based builds"
  if d=="BLUETOOTH": return "devices with Bluetooth LE capability"
  if d=="USB": return "devices with USB"
  if d=="USE_USB_HID": return "devices that support USB HID (Espruino Pico and Espruino WiFi)"
  if d=="USE_AES": return "devices that support AES (Espruino Pico, Espruino WiFi or Linux)"
  if d=="USE_SHA256": return "devices that support SHA256 (Espruino Pico, Espruino WiFi, Espruino BLE devices or Linux)"
  if d=="USE_SHA512": return "devices that support SHA512 (Espruino Pico, Espruino WiFi, Espruino BLE devices or Linux)"
  if d=="USE_CRYPTO": return "devices that support Crypto Functionality (Espruino Pico, Original, Espruino WiFi, Espruino BLE devices, Linux or ESP8266)"
  if d=="USE_FLASHFS": return "devices with filesystem in Flash support enabled (ESP32 only)"
  if d=="USE_TERMINAL": return "devices with VT100 terminal emulation enabled (Pixl.js only)"
  if d=="USE_TELNET": return "devices with Telnet enabled (Linux, ESP8266 and ESP32)"
  print("WARNING: Unknown ifdef '"+d+"' in common.get_ifdef_description")
  return d

def get_script_dir():
        return os.path.dirname(os.path.realpath(__file__))

def get_version():
        # Warning: the same release label derivation is also in the Makefile
        scriptdir = get_script_dir()
        jsutils = scriptdir+"/../src/jsutils.h"
        version = re.compile("^.*JS_VERSION.*\"(.*)\"");
        alt_release = os.getenv("ALT_RELEASE")
        if alt_release == None:
          # Default release labeling based on commits since last release tag
          latest_release = subprocess.check_output('git tag | grep RELEASE_ | sort | tail -1', shell=True).strip()
          commits_since_release = subprocess.check_output('git log --oneline '+latest_release.decode("utf-8")+'..HEAD | wc -l', shell=True).decode("utf-8").strip()
        else:
          # Alternate release labeling with fork name (in ALT_RELEASE env var) plus branch
          # name plus commit SHA
          sha = subprocess.check_output('git rev-parse --short HEAD', shell=True).strip()
          branch = subprocess.check_output('git name-rev --name-only HEAD', shell=True).strip()
          commits_since_release = alt_release + '_' + branch + '_' + sha
        for line in open(jsutils):
            match = version.search(line);
            if (match != None):
                v = match.group(1);
                if commits_since_release=="0": return v
                else: return v+"."+commits_since_release
        return "UNKNOWN"


def get_name_or_space(jsondata):
        if "name" in jsondata: return jsondata["name"]
        return ""

def get_bootloader_size(board):
        if board.chip["family"]=="STM32F4": return 16*1024; # 16kb Pages, so we have no choice
        return 10*1024;

# On normal chips this is 0x00000000
# On boards with bootloaders it's generally + 10240
# On F401, because of the setup of pages we put the bootloader in the first 16k, then in the 16+16+16 we put the saved code, and then finally we but the binary somewhere else
def get_espruino_binary_address(board):
        if "place_text_section" in board.chip:
          return board.chip["place_text_section"]
        if "bootloader" in board.info and board.info["bootloader"]==1:
          return get_bootloader_size(board);
        return 0;

def get_board_binary_name(board):
        return board.info["binary_name"].replace("%v", get_version());

#!/bin/false

# This file is part of Espruino, a JavaScript interpreter for Microcontrollers
#
# Copyright (C) 2013 <NAME> <<EMAIL>>
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# ----------------------------------------------------------------------------------------
# Reads board information from boards/BOARDNAME.py - used by build_board_docs,
# build_pininfo, and build_platform_config
# ----------------------------------------------------------------------------------------

# Global
import subprocess;
import re;
import json;
import sys;
import os;
import importlib;
# Local
import pinutils;

silent = os.getenv("SILENT");
if silent:
  class Discarder(object):
    def write(self, text):
        pass # do nothing
    def flush(self):
        pass # do nothing
  # now discard everything coming out of stdout
  sys.stdout = Discarder()

# http://stackoverflow.com/questions/4814970/subprocess-check-output-doesnt-seem-to-exist-python-2-6-5
if "check_output" not in dir( subprocess ):
    def f(*popenargs, **kwargs):
        if 'stdout' in kwargs:
            raise ValueError('stdout argument not allowed, it will be overridden.')
        process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
        output, unused_err = process.communicate()
        retcode = process.poll()
        if retcode:
            cmd = kwargs.get("args")
            if cmd is None:
                cmd = popenargs[0]
            raise subprocess.CalledProcessError(retcode, cmd)
        return output
    subprocess.check_output = f


# Scans files for comments of the form /*JSON......*/
#
# Comments look like:
#
#/*JSON{ "type":"staticmethod|staticproperty|constructor|method|property|function|variable|class|library|idle|init|kill|EV_xxx",
#                      // class = built-in class that does not require instantiation
#                      // library = built-in class that needs require('classname')
#                      // idle = function to run on idle regardless
#                      // init = function to run on initialisation
#                      // kill = function to run on deinitialisation
#                      // EV_xxx = Something to be called with a character in an IRQ when it is received (eg. EV_SERIAL1)
#         "class" : "Double", "name" : "doubleToIntBits",
#         "needs_parentName":true,           // optional - if for a method, this makes the first 2 args parent+parentName (not just parent)
#         "generate_full|generate|wrap" : "*(JsVarInt*)&x", // if generate=false, it'll only be used for docs
#         "generate_js" : "full/file/path.js", // you can supply a JS file instead of 'generate' above. Should be of the form '(function(args) { ... })'
#         "description" : " Convert the floating point value given into an integer representing the bits contained in it",
#         "params" : [ [ "x" , "float|int|int32|bool|pin|JsVar|JsVarName|JsVarArray", "A floating point number"] ],
#                               // float - parses into a JsVarFloat which is passed to the function
#                               // int - parses into a JsVarInt which is passed to the function
#                               // int32 - parses into a 32 bit int
#                               // bool - parses into a boolean
#                               // pin - parses into a pin
#                               // JsVar - passes a JsVar* to the function (after skipping names)
#                               // JsVarArray - parses this AND ANY SUBSEQUENT ARGUMENTS into a JsVar of type JSV_ARRAY. THIS IS ALWAYS DEFINED, EVEN IF ZERO LENGTH. Currently it must be the only parameter
#         "return" : ["int|float|JsVar", "The integer representation of x"],
#         "return_object" : "ObjectName", // optional - used for tern's code analysis - so for example we can do hints for openFile(...).yyy
#         "no_create_links":1                // optional - if this is set then hyperlinks are not created when this name is mentioned (good example = bit() )
#         "not_real_object" : "anything",    // optional - for classes, this means we shouldn't treat this as a built-in object, as internally it isn't stored in a JSV_OBJECT
#         "prototype" : "Object",    // optional - for classes, this is what their prototype is. It's particlarly helpful if not_real_object, because there is no prototype var in that case
#         "check" : "jsvIsFoo(var)", // for classes - this is code that returns true if 'var' is of the given type
#         "ifndef" : "SAVE_ON_FLASH", // if the given preprocessor macro is defined, don't implement this
#         "ifdef" : "USE_LCD_FOO", // if the given preprocessor macro isn't defined, don't implement this
#         "#if" : "A>2", // add a #if statement in the generated C file (ONLY if type==object)
#}*/
#
# description can be an array of strings as well as a simple string (in which case each element is separated by a newline),
# and adding ```sometext``` in the description surrounds it with HTML code tags
#
# COMMAND LINE OPTIONS
# -Ddefinition
# -BBOARDFILE

def get_jsondata(is_for_document, parseArgs = True, board = False):
    scriptdir = os.path.dirname	(os.path.realpath(__file__))
    print("Script location "+scriptdir)
    os.chdir(scriptdir+"/..")

    ignore_ifdefs = is_for_document

    # C files that we'll scan for JSON data
    jswraps = []
    # definitions that are used when evaluating IFDEFs/etc
    defines = []

    explicit_files = False
    if parseArgs and len(sys.argv)>1:
      print("Using files from command line")
      for i in range(1,len(sys.argv)):
        arg = sys.argv[i]
        if arg[0]=="-":
          if arg[1]=="D":
            defines.append(arg[2:])
          elif arg[1]=="B":
            print("BOARD "+arg[2:]);
            print("Now ignore_ifdefs = False");
            ignore_ifdefs = False
            board = importlib.import_module(arg[2:])            
          elif arg[1]=="F":
            "" # -Fxxx.yy in args is filename xxx.yy, which is mandatory for build_jswrapper.py
          else:
            print("Unknown command-line option")
            exit(1)
        elif arg[-2:]==".c": 
          # C file, all good
          explicit_files = True
          jswraps.append(arg)
        else:
          print("WARNING: Ignoring unknown file type: " + arg)
    if not explicit_files:
      print("Scanning for jswrap.c files")
      jswraps = subprocess.check_output(["find", ".", "-name", "jswrap*.c"]).strip().split("\n")

    if board:
      if "usart" in board.chip: defines.append("USART_COUNT="+str(board.chip["usart"]));
      if "spi" in board.chip: defines.append("SPI_COUNT="+str(board.chip["spi"]));
      if "i2c" in board.chip: defines.append("I2C_COUNT="+str(board.chip["i2c"]));
      if "USB" in board.devices: defines.append("defined(USB)=True");
      else: defines.append("defined(USB)=False");
      if "build" in board.info:
        if "defines" in board.info["build"]:
          for i in board.info["build"]["defines"]:
            print("board.defines: " + i);
            defines.append(i)
        if "makefile" in board.info["build"]:
          for i in board.info["build"]["makefile"]:           
            print("board.makefile: " + i);
            i = i.strip()
            if i.startswith("DEFINES"): 
              defs = i[7:].strip()[2:].strip().split() # array of -Dsomething
              for d in defs: 
                if not d.startswith("-D"):
                  print("WARNING: expecting -Ddefine, got " + d)
                defines.append(d[2:])

    if len(defines)>1:
      print("Got #DEFINES:")
      for d in defines: print("   "+d)

    jsondatas = []
    for jswrap in jswraps:
      # ignore anything from archives
      if jswrap.startswith("./archives/"): continue

      # now scan
      print("Scanning "+jswrap)
      code = open(jswrap, "r").read()

      if is_for_document and not explicit_files and "DO_NOT_INCLUDE_IN_DOCS" in code:
        print("FOUND 'DO_NOT_INCLUDE_IN_DOCS' IN FILE "+jswrap)
        continue

      for comment in re.findall(r"/\*JSON.*?\*/", code, re.VERBOSE | re.MULTILINE | re.DOTALL):
        charnumber = code.find(comment)
        linenumber = 1+code.count("\n", 0, charnumber)
        # Strip off /*JSON .. */ bit
        comment = comment[6:-2]

        endOfJson = comment.find("\n}")+2;
        jsonstring = comment[0:endOfJson];
        description =  comment[endOfJson:].strip();
#            print("Parsing "+jsonstring)
        try:
          jsondata = json.loads(jsonstring)
          if len(description): jsondata["description"] = description;
          jsondata["filename"] = jswrap
          if jswrap[-2:]==".c":
            jsondata["include"] = jswrap[:-2]+".h"
          jsondata["githublink"] = "https://github.com/espruino/Espruino/blob/master/"+jswrap+"#L"+str(linenumber)

          dropped_prefix = "Dropped "
          if "name" in jsondata: dropped_prefix += jsondata["name"]+" "
          elif "class" in jsondata: dropped_prefix += jsondata["class"]+" "
          drop = False
          if not ignore_ifdefs:
            if ("generate" in jsondata) and jsondata["generate"]==False and not is_for_document:
              print(dropped_prefix+" because of generate=false")
              drop = True
            if ("ifndef" in jsondata) and (jsondata["ifndef"] in defines):
              print(dropped_prefix+" because of #ifndef "+jsondata["ifndef"])
              drop = True
            if ("ifdef" in jsondata) and not (jsondata["ifdef"] in defines):
              print(dropped_prefix+" because of #ifdef "+jsondata["ifdef"])
              drop = True
            if ("#ifdef" in jsondata) or ("#ifndef" in jsondata):
              sys.stderr.write( "'#ifdef' where 'ifdef' should be used in " + jsonstring + " - "+str(sys.exc_info()[0]) + "\n" )
              exit(1)
            if ("if" in jsondata):
              sys.stderr.write( "'if' where '#if' should be used in " + jsonstring + " - "+str(sys.exc_info()[0]) + "\n" )
              exit(1)
            if ("#if" in jsondata):
              expr = jsondata["#if"]
              for defn in defines:
                expr = expr.replace("defined("+defn+")", "True");
                if defn.find('=')!=-1:
                  dname = defn[:defn.find('=')]
                  dkey = defn[defn.find('=')+1:]
                  expr = expr.replace("defined("+dname+")", "True");
                  expr = expr.replace(dname, dkey);
              # Now replace any defined(...) we haven't heard of with false
              expr = re.sub(r"defined\([^\)]*\)", "False", expr)
              expr = expr.replace("||","or").replace("&&","and");
              expr = expr.replace("!","not ");
              try:
                r = eval(expr)
              except:
                print("WARNING: error evaluating '"+expr+"' - from '"+jsondata["#if"]+"'")
                r = True
              if not r:
                print(dropped_prefix+" because of #if "+jsondata["#if"]+ " -> "+expr)
                drop = True
          if not drop:
            jsondatas.append(jsondata)
        except ValueError as e:
          sys.stderr.write( "JSON PARSE FAILED for " +  jsonstring + " - "+ str(e) + "\n")
          exit(1)
        except:
          sys.stderr.write( "JSON PARSE FAILED for " + jsonstring + " - "+str(sys.exc_info()[0]) + "\n" )
          exit(1)
    print("Scanning finished.")

    if board:
      for device in pinutils.SIMPLE_DEVICES:
        if device in board.devices:
          jsondatas.append({
            "type" : "variable",
            "name" : device,
            "generate_full" : device+"_PININDEX",
            "return" : ["pin", device],
            "filename" : "BOARD.py",
            "include" : "platform_config.h"
          })
      if "LED1" in board.devices:
        jsondatas.append({
          "type" : "variable",
          "name" : "LED",
          "generate_full" : "LED1_PININDEX",
          "return" : ["pin", "LED1"],
          "filename" : "BOARD.py",
          "include" : "platform_config.h"
        })
      if "BTN1" in board.devices:
        jsondatas.append({
          "type" : "variable",
          "name" : "BTN",
          "generate_full" : "BTN1_PININDEX",
          "return" : ["pin", "Button 1"],
          "filename" : "BOARD.py",
          "include" : "platform_config.h"
        })
    return jsondatas

# Takes the data from get_jsondata and restructures it in prepartion for output as JS
#
# Results look like:,
#{
#  "Pin": {
#    "desc": [
#      "This is the built-in class for Pins, such as D0,D1,LED1, or BTN",
#      "You can call the methods on Pin, or you can use Wiring-style functions such as digitalWrite"
#    ],
#    "methods": {
#      "read": {
#        "desc": "Returns the input state of the pin as a boolean",
#        "params": [],
#        "return": [
#          "bool",
#          "Whether pin is a logical 1 or 0"
#        ]
#      },
#      "reset": {
#        "desc": "Sets the output state of the pin to a 0",
#        "params": [],
#        "return": []
#      },
#      ...
#    },
#    "props": {},
#    "staticmethods": {},
#    "staticprops": {}
#  },
#  "print": {
#    "desc": "Print the supplied string",
#    "return": []
#  },
#  ...
#}
#

def get_struct_from_jsondata(jsondata):
  context = {"modules": {}}

  def checkClass(details):
    cl = details["class"]
    if not cl in context:
      context[cl] = {"type": "class", "methods": {}, "props": {}, "staticmethods": {}, "staticprops": {}, "desc": details.get("description", "")}
    return cl

  def addConstructor(details):
    cl = checkClass(details)
    context[cl]["constructor"] = {"params": details.get("params", []), "return": details.get("return", []), "desc": details.get("description", "")}

  def addMethod(details, type = ""):
    cl = checkClass(details)
    context[cl][type + "methods"][details["name"]] = {"params": details.get("params", []), "return": details.get("return", []), "desc": details.get("description", "")}

  def addProp(details, type = ""):
    cl = checkClass(details)
    context[cl][type + "props"][details["name"]] = {"return": details.get("return", []), "desc": details.get("description", "")}

  def addFunc(details):
    context[details["name"]] = {"type": "function", "return": details.get("return", []), "desc": details.get("description", "")}

  def addObj(details):
    context[details["name"]] = {"type": "object", "instanceof": details.get("instanceof", ""), "desc": details.get("description", "")}

  def addLib(details):
    context["modules"][details["class"]] = {"desc": details.get("description", "")}
  def addVar(details):
    return

  for data in jsondata:
    type = data["type"]
    if type=="class":
      checkClass(data)
    elif type=="constructor":
      addConstructor(data)
    elif type=="method":
      addMethod(data)
    elif type=="property":
      addProp(data)
    elif type=="staticmethod":
      addMethod(data, "static")
    elif type=="staticproperty":
      addProp(data, "static")
    elif type=="function":
      addFunc(data)
    elif type=="object":
      addObj(data)
    elif type=="library":
      addLib(data)
    elif type=="variable":
      addVar(data)
    else:
      print(json.dumps(data, sort_keys=True, indent=2))

  return context

def get_includes_from_jsondata(jsondatas):
        includes = []
        for jsondata in jsondatas:
          if "include" in jsondata:
            include = jsondata["include"]
            if not include in includes:
              includes.append(include)
        return includes

def is_property(jsondata):
  return jsondata["type"]=="property" or jsondata["type"]=="staticproperty" or jsondata["type"]=="variable"

def is_function(jsondata):
  return jsondata["type"]=="function" or jsondata["type"]=="method"

def get_prefix_name(jsondata):
  if jsondata["type"]=="event": return "event"
  if jsondata["type"]=="constructor": return "constructor"
  if jsondata["type"]=="function": return "function"
  if jsondata["type"]=="method": return "function"
  if jsondata["type"]=="variable": return "variable"
  if jsondata["type"]=="property": return "property"
  return ""

def get_ifdef_description(d):
  if d=="SAVE_ON_FLASH": return "devices with low flash memory"
  if d=="SAVE_ON_FLASH_EXTREME": return "devices with extremely low flash memory (eg. HYSTM32_28)"
  if d=="STM32": return "STM32 devices (including Espruino Original, Pico and WiFi)"
  if d=="STM32F1": return "STM32F1 devices (including Original Espruino Board)"
  if d=="NRF52": return "NRF52 devices (like Puck.js, Pixl.js and MDBT42Q)"
  if d=="PUCKJS": return "Puck.js devices"
  if d=="PIXLJS": return "Pixl.js boards"
  if d=="ESPRUINOWIFI": return "Espruino WiFi boards"
  if d=="ESPRUINOBOARD": return "'Original' Espruino boards"
  if d=="PICO": return "Espruino Pico boards"
  if d=="BANGLEJS": return "Bangle.js smartwatches"
  if d=="ESP8266": return "ESP8266 boards running Espruino"
  if d=="ESP32": return "ESP32 boards"
  if d=="EFM32": return "EFM32 devices"
  if d=="USE_LCD_SDL": return "Linux with SDL support compiled in"
  if d=="USE_TLS": return "devices with TLS and SSL support (Espruino Pico and Espruino WiFi only)"
  if d=="RELEASE": return "release builds"
  if d=="DEBUG": return "debug builds"
  if d=="LINUX": return "Linux-based builds"
  if d=="BLUETOOTH": return "devices with Bluetooth LE capability"
  if d=="USB": return "devices with USB"
  if d=="USE_USB_HID": return "devices that support USB HID (Espruino Pico and Espruino WiFi)"
  if d=="USE_AES": return "devices that support AES (Espruino Pico, Espruino WiFi or Linux)"
  if d=="USE_SHA256": return "devices that support SHA256 (Espruino Pico, Espruino WiFi, Espruino BLE devices or Linux)"
  if d=="USE_SHA512": return "devices that support SHA512 (Espruino Pico, Espruino WiFi, Espruino BLE devices or Linux)"
  if d=="USE_CRYPTO": return "devices that support Crypto Functionality (Espruino Pico, Original, Espruino WiFi, Espruino BLE devices, Linux or ESP8266)"
  if d=="USE_FLASHFS": return "devices with filesystem in Flash support enabled (ESP32 only)"
  if d=="USE_TERMINAL": return "devices with VT100 terminal emulation enabled (Pixl.js only)"
  if d=="USE_TELNET": return "devices with Telnet enabled (Linux, ESP8266 and ESP32)"
  print("WARNING: Unknown ifdef '"+d+"' in common.get_ifdef_description")
  return d

def get_script_dir():
        return os.path.dirname(os.path.realpath(__file__))

def get_version():
        # Warning: the same release label derivation is also in the Makefile
        scriptdir = get_script_dir()
        jsutils = scriptdir+"/../src/jsutils.h"
        version = re.compile("^.*JS_VERSION.*\"(.*)\"");
        alt_release = os.getenv("ALT_RELEASE")
        if alt_release == None:
          # Default release labeling based on commits since last release tag
          latest_release = subprocess.check_output('git tag | grep RELEASE_ | sort | tail -1', shell=True).strip()
          commits_since_release = subprocess.check_output('git log --oneline '+latest_release.decode("utf-8")+'..HEAD | wc -l', shell=True).decode("utf-8").strip()
        else:
          # Alternate release labeling with fork name (in ALT_RELEASE env var) plus branch
          # name plus commit SHA
          sha = subprocess.check_output('git rev-parse --short HEAD', shell=True).strip()
          branch = subprocess.check_output('git name-rev --name-only HEAD', shell=True).strip()
          commits_since_release = alt_release + '_' + branch + '_' + sha
        for line in open(jsutils):
            match = version.search(line);
            if (match != None):
                v = match.group(1);
                if commits_since_release=="0": return v
                else: return v+"."+commits_since_release
        return "UNKNOWN"


def get_name_or_space(jsondata):
        if "name" in jsondata: return jsondata["name"]
        return ""

def get_bootloader_size(board):
        if board.chip["family"]=="STM32F4": return 16*1024; # 16kb Pages, so we have no choice
        return 10*1024;

# On normal chips this is 0x00000000
# On boards with bootloaders it's generally + 10240
# On F401, because of the setup of pages we put the bootloader in the first 16k, then in the 16+16+16 we put the saved code, and then finally we but the binary somewhere else
def get_espruino_binary_address(board):
        if "place_text_section" in board.chip:
          return board.chip["place_text_section"]
        if "bootloader" in board.info and board.info["bootloader"]==1:
          return get_bootloader_size(board);
        return 0;

def get_board_binary_name(board):
        return board.info["binary_name"].replace("%v", get_version());
#!/bin/false # This file is part of Espruino, a JavaScript interpreter for Microcontrollers # # Copyright (C) 2013 <NAME> <<EMAIL>> # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # ---------------------------------------------------------------------------------------- # Reads board information from boards/BOARDNAME.py - used by build_board_docs, # build_pininfo, and build_platform_config # ---------------------------------------------------------------------------------------- # Global # Local # do nothing # do nothing # now discard everything coming out of stdout # http://stackoverflow.com/questions/4814970/subprocess-check-output-doesnt-seem-to-exist-python-2-6-5 # Scans files for comments of the form /*JSON......*/ # # Comments look like: # #/*JSON{ "type":"staticmethod|staticproperty|constructor|method|property|function|variable|class|library|idle|init|kill|EV_xxx", #                      // class = built-in class that does not require instantiation #                      // library = built-in class that needs require('classname') #                      // idle = function to run on idle regardless #                      // init = function to run on initialisation #                      // kill = function to run on deinitialisation #                      // EV_xxx = Something to be called with a character in an IRQ when it is received (eg. EV_SERIAL1) #         "class" : "Double", "name" : "doubleToIntBits", #         "needs_parentName":true,           // optional - if for a method, this makes the first 2 args parent+parentName (not just parent) #         "generate_full|generate|wrap" : "*(JsVarInt*)&x", // if generate=false, it'll only be used for docs #         "generate_js" : "full/file/path.js", // you can supply a JS file instead of 'generate' above. Should be of the form '(function(args) { ... })' #         "description" : " Convert the floating point value given into an integer representing the bits contained in it", #         "params" : [ [ "x" , "float|int|int32|bool|pin|JsVar|JsVarName|JsVarArray", "A floating point number"] ], #                               // float - parses into a JsVarFloat which is passed to the function #                               // int - parses into a JsVarInt which is passed to the function #                               // int32 - parses into a 32 bit int #                               // bool - parses into a boolean #                               // pin - parses into a pin #                               // JsVar - passes a JsVar* to the function (after skipping names) #                               // JsVarArray - parses this AND ANY SUBSEQUENT ARGUMENTS into a JsVar of type JSV_ARRAY. THIS IS ALWAYS DEFINED, EVEN IF ZERO LENGTH. Currently it must be the only parameter #         "return" : ["int|float|JsVar", "The integer representation of x"], #         "return_object" : "ObjectName", // optional - used for tern's code analysis - so for example we can do hints for openFile(...).yyy #         "no_create_links":1                // optional - if this is set then hyperlinks are not created when this name is mentioned (good example = bit() ) #         "not_real_object" : "anything",    // optional - for classes, this means we shouldn't treat this as a built-in object, as internally it isn't stored in a JSV_OBJECT #         "prototype" : "Object",    // optional - for classes, this is what their prototype is. It's particlarly helpful if not_real_object, because there is no prototype var in that case #         "check" : "jsvIsFoo(var)", // for classes - this is code that returns true if 'var' is of the given type #         "ifndef" : "SAVE_ON_FLASH", // if the given preprocessor macro is defined, don't implement this #         "ifdef" : "USE_LCD_FOO", // if the given preprocessor macro isn't defined, don't implement this #         "#if" : "A>2", // add a #if statement in the generated C file (ONLY if type==object) #}*/ # # description can be an array of strings as well as a simple string (in which case each element is separated by a newline), # and adding ```sometext``` in the description surrounds it with HTML code tags # # COMMAND LINE OPTIONS # -Ddefinition # -BBOARDFILE # C files that we'll scan for JSON data # definitions that are used when evaluating IFDEFs/etc # -Fxxx.yy in args is filename xxx.yy, which is mandatory for build_jswrapper.py # C file, all good # array of -Dsomething #DEFINES:") # ignore anything from archives # now scan # Strip off /*JSON .. */ bit #            print("Parsing "+jsonstring) #ifndef "+jsondata["ifndef"]) #ifdef "+jsondata["ifdef"]) # Now replace any defined(...) we haven't heard of with false #if "+jsondata["#if"]+ " -> "+expr) # Takes the data from get_jsondata and restructures it in prepartion for output as JS # # Results look like:, #{ #  "Pin": { #    "desc": [ #      "This is the built-in class for Pins, such as D0,D1,LED1, or BTN", #      "You can call the methods on Pin, or you can use Wiring-style functions such as digitalWrite" #    ], #    "methods": { #      "read": { #        "desc": "Returns the input state of the pin as a boolean", #        "params": [], #        "return": [ #          "bool", #          "Whether pin is a logical 1 or 0" #        ] #      }, #      "reset": { #        "desc": "Sets the output state of the pin to a 0", #        "params": [], #        "return": [] #      }, #      ... #    }, #    "props": {}, #    "staticmethods": {}, #    "staticprops": {} #  }, #  "print": { #    "desc": "Print the supplied string", #    "return": [] #  }, #  ... #} # # Warning: the same release label derivation is also in the Makefile # Default release labeling based on commits since last release tag # Alternate release labeling with fork name (in ALT_RELEASE env var) plus branch # name plus commit SHA # 16kb Pages, so we have no choice # On normal chips this is 0x00000000 # On boards with bootloaders it's generally + 10240 # On F401, because of the setup of pages we put the bootloader in the first 16k, then in the 16+16+16 we put the saved code, and then finally we but the binary somewhere else