| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 |
- # Web UI for configuration of the CMSIS-DSP Build
- #
- # How to install
- # pip install streamlit
- #
- # How to use
- # streamlit run cmsisdspconfig.py
- #
- import streamlit as st
- import textwrap
- import re
- st.set_page_config(page_title="CMSIS-DSP Configuration",layout="wide" )
- # Options requiring a special management
- NOTSTANDARD=["allTables","allInterpolations","allFFTs","Float16"]
- HELIUM=False
- config={}
- # Used in UI
- config["allTables"] = True
- config["allFFTs"] = True
- config["allInterpolations"] = True
- config["MVEI"]=False
- config["MVEF"]=False
- config["NEON"]=False
- config["HELIUM"]=False
- config["HELIUMEXPERIMENTAL"]=False
- config["Float16"]=True
- config["HOST"]=False
- config["COS_F32"]=False
- config["COS_Q31"]=False
- config["COS_Q15"]=False
- config["SIN_F32"]=False
- config["SIN_Q31"]=False
- config["SIN_Q15"]=False
- config["SIN_COS_F32"]=False
- config["SIN_COS_Q31"]=False
- config["SQRT_Q31"]=False
- config["LMS_NORM_Q31"]=False
- config["LMS_NORM_Q15"]=False
- config["CMPLX_MAG_Q31"]=False
- config["CMPLX_MAG_Q15"]=False
- config["CMPLX_MAG_FAST_Q15"]=False
- config["CFFT_RADIX2_Q15"]=False
- config["CFFT_RADIX4_Q15"]=False
- config["CFFT_RADIX2_Q31"]=False
- config["CFFT_RADIX4_Q31"]=False
- config["BASICMATH"]=True
- config["COMPLEXMATH"]=True
- config["CONTROLLER"]=True
- config["FASTMATH"]=True
- config["FILTERING"]=True
- config["MATRIX"]=True
- config["STATISTICS"]=True
- config["SUPPORT"]=True
- config["TRANSFORM"]=True
- config["SVM"]=True
- config["BAYES"]=True
- config["DISTANCE"]=True
- config["INTERPOLATION"]=True
- config["QUATERNIONMATH"]=True
- config["LOOPUNROLL"]=True
- config["ROUNDING"]=False
- config["MATRIXCHECK"]=False
- config["AUTOVECTORIZE"] = False
- # Used as options in command line
- # in case the UI option is worded differently
- realname={}
- realname["COS_F32"]="ARM_COS_F32"
- realname["COS_Q31"]="ARM_COS_Q31"
- realname["COS_Q15"]="ARM_COS_Q15"
- realname["SIN_F32"]="ARM_SIN_F32"
- realname["SIN_Q31"]="ARM_SIN_Q31"
- realname["SIN_Q15"]="ARM_SIN_Q15"
- realname["SIN_COS_F32"]="ARM_SIN_COS_F32"
- realname["SIN_COS_Q31"]="ARM_SIN_COS_Q31"
- realname["SQRT_Q31"]="ARM_SQRT_Q31"
- realname["LMS_NORM_Q31"]="ARM_LMS_NORM_Q31"
- realname["LMS_NORM_Q15"]="ARM_LMS_NORM_Q15"
- realname["CMPLX_MAG_Q31"]="ARM_CMPLX_MAG_Q31"
- realname["CMPLX_MAG_Q15"]="ARM_CMPLX_MAG_Q15"
- realname["CMPLX_MAG_FAST_Q15"]="ARM_CMPLX_MAG_FAST_Q15"
- realname["CFFT_RADIX2_Q15"]="ARM_CFFT_RADIX2_Q15"
- realname["CFFT_RADIX4_Q15"]="ARM_CFFT_RADIX4_Q15"
- realname["CFFT_RADIX2_Q31"]="ARM_CFFT_RADIX2_Q31"
- realname["CFFT_RADIX4_Q31"]="ARM_CFFT_RADIX4_Q31"
- defaulton={}
- defaulton["LOOPUNROLL"]=True
- defaulton["BASICMATH"]=True
- defaulton["COMPLEXMATH"]=True
- defaulton["CONTROLLER"]=True
- defaulton["FASTMATH"]=True
- defaulton["FILTERING"]=True
- defaulton["MATRIX"]=True
- defaulton["STATISTICS"]=True
- defaulton["SUPPORT"]=True
- defaulton["TRANSFORM"]=True
- defaulton["SVM"]=True
- defaulton["BAYES"]=True
- defaulton["DISTANCE"]=True
- defaulton["INTERPOLATION"]=True
- defaulton["QUATERNIONMATH"]=True
- CFFTSIZE=[16,32,64,128,256,512,1024,2048,4096]
- CFFTDATATYPE=['F64','F32','F16','Q31','Q15']
- RFFTFASTSIZE=[32,64,128,256,512,1024,2048,4096]
- RFFTFASTDATATYPE=['F64','F32','F16']
- RFFTSIZE=[32,64,128,256,512,1024,2048,4096,8192]
- RFFTDATATYPE=['F32','Q31','Q15']
- DCTSIZE=[128,512,2048,8192]
- DCTDATATYPE=['F32','Q31','Q15']
- def joinit(iterable, delimiter):
- # Intersperse a delimiter between element of a list
- it = iter(iterable)
- yield next(it)
- for x in it:
- yield delimiter
- yield x
- def options(l):
- return("".join(joinit(l," ")))
- def computeCmakeOptions(config):
- global defaulton
- cmake={}
- if not config["allTables"]:
- cmake["CONFIGTABLE"]=True
- if config["allInterpolations"]:
- cmake["ALLFAST"]=True
- if config["allFFTs"]:
- cmake["ALLFFT"]=True
- if config["Float16"]:
- cmake["FLOAT16"]=True
- else:
- cmake["DISABLEFLOAT16"]=True
- for c in config:
- if not (c in NOTSTANDARD):
- if c in defaulton:
- if not config[c]:
- if c in realname:
- cmake[realname[c]]=False
- else:
- cmake[c]=False
- else:
- if config[c]:
- if c in realname:
- cmake[realname[c]]=True
- else:
- cmake[c]=True
- return cmake
- def removeDuplicates(l):
- return list(dict.fromkeys(l))
- def genCMakeOptions(config):
- r=[]
- cmake = computeCmakeOptions(config)
- for c in cmake:
- if cmake[c]:
- r.append("-D%s=ON" % c)
- else:
- r.append("-D%s=OFF" % c)
- return(removeDuplicates(r),cmake)
- def test(cmake,s):
- global defaulton
- if s in defaulton and not (s in cmake):
- return True
- return(s in cmake and cmake[s])
- def cfftCF32Config(cmake,size):
- result=[]
- if test(cmake,"CFFT_F32_%d" % size):
- a="-DARM_TABLE_TWIDDLECOEF_F32_%d" % size
- if HELIUM:
- b = "-DARM_TABLE_BITREVIDX_FXT_%d" % size
- else:
- b = "-DARM_TABLE_BITREVIDX_FLT_%d" % size
- result=[a,b]
- return(result)
- def cfftCF16Config(cmake,size):
- result=[]
- if test(cmake,"CFFT_F16_%d" % size):
- result =["-DARM_TABLE_TWIDDLECOEF_F16_%d" % size]
- result.append("-DARM_TABLE_BITREVIDX_FXT_%d" % size)
- result.append("-DARM_TABLE_BITREVIDX_FLT_%d" % size)
- return(result)
- def cfftCF64Config(cmake,size):
- result=[]
- if test(cmake,"CFFT_F64_%d" % size):
- result =["-DARM_TABLE_TWIDDLECOEF_F64_%d" % size]
- result.append("-DARM_TABLE_BITREVIDX_FLT64_%d" % size)
- return(result)
- def cfftCFixedConfig(cmake,dt,size):
- result=[]
- if test(cmake,"CFFT_%s_%d" % (dt,size)):
- a="-DARM_TABLE_TWIDDLECOEF_%s_%d" % (dt,size)
- b = "-DARM_TABLE_BITREVIDX_FXT_%d" % size
- result=[a,b]
- return(result)
- def crfftFastCF64Config(cmake,size):
- result=[]
- s1 = size >> 1
- if test(cmake,"RFFT_FAST_F64_%d" % size):
- result =[]
- result.append("-DARM_TABLE_TWIDDLECOEF_F64_%d" % s1)
- result.append("-DARM_TABLE_BITREVIDX_FLT64_%d" % s1)
- result.append("-DARM_TABLE_TWIDDLECOEF_RFFT_F64_%d" % size)
- result.append("-DARM_TABLE_TWIDDLECOEF_F64_%d" % s1)
-
- return(result)
- def crfftFastCF32Config(cmake,size):
- result=[]
- s1 = size >> 1
- if test(cmake,"RFFT_FAST_F32_%d" % size):
- result =[]
- result.append("-DARM_TABLE_TWIDDLECOEF_F32_%d" % s1)
- result.append("-DARM_TABLE_BITREVIDX_FLT_%d" % s1)
- result.append("-DARM_TABLE_TWIDDLECOEF_RFFT_F32_%d" % size)
-
- return(result)
- def crfftFastCF16Config(cmake,size):
- result=[]
- s1 = size >> 1
- if test(cmake,"RFFT_FAST_F16_%d" % size):
- result =[]
- result.append("-DARM_TABLE_TWIDDLECOEF_F16_%d" % s1)
- result.append("-DARM_TABLE_BITREVIDX_FLT_%d" % s1)
- result.append("-DARM_TABLE_BITREVIDX_FXT_%d" % s1)
- result.append("-DARM_TABLE_TWIDDLECOEF_RFFT_F16_%d" % size)
-
- return(result)
- # Deprecated RFFT used in DCT
- def crfftF32Config(cmake,size):
- result=[]
- s1 = size >> 1
- if test(cmake,"RFFT_FAST_F16_%d" % size):
- result =[]
- result.append("-DARM_TABLE_REALCOEF_F32")
- result.append("-ARM_TABLE_BITREV_%d" % s1)
- result.append("-ARM_TABLE_TWIDDLECOEF_F32_%d" % s1)
-
- return(result)
- def crfftFixedConfig(cmake,dt,size):
- result=[]
- s1 = size >> 1
- if test(cmake,"RFFT_%s_%d" % (dt,size)):
- result =[]
- result.append("-DARM_TABLE_REALCOEF_%s" % dt)
- result.append("-DARM_TABLE_TWIDDLECOEF_%s_%d" % (dt,s1))
- result.append("-DARM_TABLE_BITREVIDX_FXT_%d" % s1)
-
- return(result)
- def dctConfig(cmake,dt,size):
- result=[]
- if test(cmake,"DCT4_%s_%d" % (dt,size)):
- result =[]
- result.append("-DARM_TABLE_DCT4_%s_%d" % (dt,size))
- result.append("-DARM_TABLE_REALCOEF_F32")
- result.append("-DARM_TABLE_BITREV_1024" )
- result.append("-DARM_TABLE_TWIDDLECOEF_%s_4096" % dt)
-
- return(result)
- # Convert cmake options to make flags
- def interpretCmakeOptions(cmake):
- r=[]
- if test(cmake,"CONFIGTABLE"):
- r.append("-DARM_DSP_CONFIG_TABLES")
- # In Make configuration we build all modules.
- # So the code for FFT and FAST maths may be included
- # so we allow the table to be included if they are needed.
- r.append("-DARM_FAST_ALLOW_TABLES")
- r.append("-DARM_FFT_ALLOW_TABLES")
- for size in CFFTSIZE:
- r += cfftCF32Config(cmake,size)
- r += cfftCF16Config(cmake,size)
- r += cfftCF64Config(cmake,size)
- r += cfftCFixedConfig(cmake,"Q31",size)
- r += cfftCFixedConfig(cmake,"Q15",size)
- for size in RFFTFASTSIZE:
- r += crfftFastCF64Config(cmake,size)
- r += crfftFastCF32Config(cmake,size)
- r += crfftFastCF16Config(cmake,size)
- for size in RFFTSIZE:
- r += crfftFixedConfig(cmake,"F32",size)
- r += crfftFixedConfig(cmake,"Q31",size)
- r += crfftFixedConfig(cmake,"Q15",size)
- for size in DCTSIZE:
- r += dctConfig(cmake,"F32",size)
- r += dctConfig(cmake,"Q31",size)
- r += dctConfig(cmake,"Q15",size)
-
-
- if test(cmake,"ALLFAST"):
- r.append("-DARM_ALL_FAST_TABLES")
- if test(cmake,"ALLFFT"):
- r.append("-DARM_ALL_FFT_TABLES")
- if test(cmake,"LOOPUNROLL"):
- r.append("-DARM_MATH_LOOPUNROLL")
- if test(cmake,"ROUNDING"):
- r.append("-DARM_MATH_ROUNDING")
- if test(cmake,"MATRIXCHECK"):
- r.append("-DARM_MATH_MATRIX_CHECK")
- if test(cmake,"AUTOVECTORIZE"):
- r.append("-DARM_MATH_AUTOVECTORIZE")
- if test(cmake,"DISABLEFLOAT16"):
- r.append("-DDISABLEFLOAT16")
- if test(cmake,"NEON"):
- r.append("-DARM_MATH_NEON")
- r.append("-DARM_MATH_NEON_EXPERIMENTAL")
- if test(cmake,"HOST"):
- r.append("-D__GNUC_PYTHON__")
-
- if test(cmake,"ARM_COS_F32"):
- r.append("-DARM_TABLE_SIN_F32")
- if test(cmake,"ARM_COS_Q31"):
- r.append("-DARM_TABLE_SIN_Q31")
- if test(cmake,"ARM_COS_Q15"):
- r.append("-DARM_TABLE_SIN_Q15")
- if test(cmake,"ARM_SIN_F32"):
- r.append("-DARM_TABLE_SIN_F32")
- if test(cmake,"ARM_SIN_Q31"):
- r.append("-DARM_TABLE_SIN_Q31")
- if test(cmake,"ARM_SIN_Q15"):
- r.append("-DARM_TABLE_SIN_Q15")
- if test(cmake,"ARM_SIN_COS_F32"):
- r.append("-DARM_TABLE_SIN_F32")
- if test(cmake,"ARM_SIN_COS_Q31"):
- r.append("-DARM_TABLE_SIN_Q31")
- if test(cmake,"ARM_SQRT_Q31"):
- r.append("-DARM_TABLE_SQRT_Q31")
- if test(cmake,"ARM_LMS_NORM_Q31"):
- r.append("-DARM_TABLE_RECIP_Q31")
- if test(cmake,"ARM_LMS_NORM_Q15"):
- r.append("-DARM_TABLE_RECIP_Q15")
- if test(cmake,"ARM_CMPLX_MAG_Q31"):
- r.append("-DARM_TABLE_FAST_SQRT_Q31_MVE")
- if test(cmake,"ARM_CMPLX_MAG_Q15"):
- r.append("-DARM_TABLE_FAST_SQRT_Q31_MVE")
- if test(cmake,"ARM_CMPLX_MAG_FAST_Q15"):
- r.append("-DARM_TABLE_FAST_SQRT_Q15_MVE")
- if test(cmake,"MVEI"):
- r.append("-DARM_MATH_MVEI")
- if test(cmake,"MVEF"):
- r.append("-DARM_MATH_MVEF")
- if test(cmake,"HELIUMEXPERIMENTAL"):
- r.append("-DARM_MATH_HELIUM_EXPERIMENTAL")
- if test(cmake,"HELIUM") or test(cmake,"MVEF") or test(cmake,"MVEI"):
- r.append("-IPrivateInclude")
- if test(cmake,"NEON") or test(cmake,"NEONEXPERIMENTAL"):
- r.append("-IComputeLibrary/Include")
- if test(cmake,"ARM_CFFT_RADIX2_Q15") or test(cmake,"ARM_CFFT_RADIX4_Q15"):
- r.append("-DARM_TABLE_TWIDDLECOEF_Q15_4096")
- r.append("-DARM_TABLE_BITREV_1024")
- if test(cmake,"ARM_CFFT_RADIX2_Q31") or test(cmake,"ARM_CFFT_RADIX4_Q31"):
- r.append("-DARM_TABLE_TWIDDLECOEF_Q31_4096")
- r.append("-DARM_TABLE_BITREV_1024")
- return (removeDuplicates(r))
- def genMakeOptions(config):
- cmake = computeCmakeOptions(config)
- r=interpretCmakeOptions(cmake)
- return(r,cmake)
- def check(config,s,name=None,comment=None):
- if comment is not None:
- st.sidebar.text(comment)
- if name is None:
- config[s]=st.sidebar.checkbox(s,value=config[s])
- else:
- config[s]=st.sidebar.checkbox(name,value=config[s])
- return(config[s])
- def genconfig(config,transform,sizes,datatypes):
- global realname
- for size in sizes:
- for dt in datatypes:
- s="%s_%s_%s" % (transform,dt,size)
- config[s] = False
- realname[s] = s
- def hasDCTF32(config):
- result=False
- for size in DCTSIZE:
- s="DCT4_F32_%s" % size
- if config[s]:
- result = True
- return(result)
- def multiselect(config,name,options):
- default=[]
- for r in options:
- if config[r]:
- default.append(r)
- result=st.sidebar.multiselect(name,options,default=default)
- for r in options:
- config[r] = False
- for r in result:
- config[r] = True
- def genui(config,transform,sizes,datatypes):
- keepF32 = True
- # RFFT F32 is deprecated and needed only for DCT4
- if transform == "RFFT":
- keepF32 = hasDCTF32(config)
- selected=st.sidebar.multiselect("Sizes",sizes)
- for size in selected:
- options=[]
- for dt in datatypes:
- if dt != "F32" or keepF32:
- s="%s_%s_%s" % (transform,dt,size)
- options.append(s)
- multiselect(config,"Nb = %d" % size,options)
- def configMake(config):
- st.sidebar.header('Table Configuration')
- st.sidebar.info("Several options to include only the tables needed in an app and minimize code size.")
- if not check(config,"allTables","All tables included"):
- if not check(config,"allFFTs","All FFT tables included"):
- st.sidebar.markdown("#### CFFT")
- genui(config,"CFFT",CFFTSIZE,CFFTDATATYPE)
- st.sidebar.info("Following transforms are using the CFFT. You need to enable the needed CFFTs above.")
- st.sidebar.markdown("#### RFFT FAST")
- genui(config,"RFFT_FAST",RFFTFASTSIZE,RFFTFASTDATATYPE)
- st.sidebar.markdown("#### DCT4")
- genui(config,"DCT4",DCTSIZE,DCTDATATYPE)
- st.sidebar.markdown("#### RFFT")
- genui(config,"RFFT",RFFTSIZE,RFFTDATATYPE)
- st.sidebar.markdown("#### Radix2 and Radix4 CFFT")
- st.sidebar.info("Those functions are deprecated")
- multiselect(config,"Radix",["CFFT_RADIX2_Q15","CFFT_RADIX4_Q15","CFFT_RADIX2_Q31","CFFT_RADIX4_Q31"])
-
- if not check(config,"allInterpolations",'All interpolation tables included'):
- selected=st.sidebar.multiselect("Functions",["Cosine","Sine","SineCosine","Normalized LMS"])
- for s in selected:
- if s == "Cosine":
- multiselect(config,"Cosine",["COS_F32","COS_Q31","COS_Q15"])
- if s == "Sine":
- multiselect(config,"Sine",["SIN_F32","SIN_Q31","SIN_Q15"])
- if s == "SineCosine":
- multiselect(config,"SineCosine",["SIN_COS_F32","SIN_COS_Q31"])
- if s == "Normalized LMS":
- multiselect(config,"Normalized LMS",["LMS_NORM_Q31","LMS_NORM_Q15"])
- if config["MVEI"]:
- st.sidebar.markdown("#### Complex Magnitude")
- multiselect(config,"Complex Magnitude",["CMPLX_MAG_Q31","CMPLX_MAG_Q15","CMPLX_MAG_FAST_Q15"])
- def configCMake(config):
- multiselect(config,"Folders",["BASICMATH",
- "COMPLEXMATH",
- "CONTROLLER",
- "FASTMATH",
- "FILTERING",
- "MATRIX",
- "STATISTICS",
- "SUPPORT",
- "TRANSFORM",
- "SVM",
- "BAYES",
- "DISTANCE",
- "INTERPOLATION","QUATERNIONMATH"])
- configMake(config)
- genconfig(config,"CFFT",CFFTSIZE,CFFTDATATYPE)
- genconfig(config,"RFFT_FAST",RFFTFASTSIZE,RFFTFASTDATATYPE)
- genconfig(config,"RFFT",RFFTSIZE,RFFTDATATYPE)
- genconfig(config,"DCT4",DCTSIZE,DCTDATATYPE)
- st.title('CMSIS-DSP Configuration')
- st.warning("It is a work in progress. Only a small subset of the combinations has been tested.")
- st.sidebar.header('Feature Configuration')
- st.sidebar.info("To build on host. All features will be enabled.")
- forHost=check(config,"HOST")
- if not forHost:
- st.sidebar.info("Enable or disable float16 support")
- check(config,"Float16")
- st.sidebar.info("Some configurations for the CMSIS-DSP code.")
- check(config,"LOOPUNROLL")
- st.sidebar.text("Decrease performances when selected:")
- check(config,"ROUNDING")
- check(config,"MATRIXCHECK")
-
- st.sidebar.header('Vector extensions')
- st.sidebar.info("Enable vector code. It is not automatic for Neon. Use of Helium will enable new options to select some interpolation tables.")
- archi=st.sidebar.selectbox("Vector",('None','Helium','Neon'))
- if archi == 'Neon':
- config["NEON"]=True
- if archi == 'Helium':
- multiselect(config,"MVE configuration",["MVEI","MVEF"])
- HELIUM=True
- st.sidebar.info("When checked some experimental versions will be enabled and may be less performant than scalar version depending on the architecture.")
- check(config,"HELIUMEXPERIMENTAL")
- if archi != 'None':
- st.sidebar.info("When autovectorization is on, pure C code will be compiled. The version with C intrinsics won't be compiled.")
- check(config,"AUTOVECTORIZE")
- st.sidebar.header('Build Method')
- st.sidebar.info("With cmake, some folders can be removed from the build.")
- selected=st.sidebar.selectbox('Select', ("Make","Cmake"),index=1)
- if selected == "Make":
- if not forHost:
- configMake(config)
- result,cmake=genMakeOptions(config)
- else:
- if not forHost:
- configCMake(config)
- result,cmake=genCMakeOptions(config)
- st.header('Build options for %s command line' % selected)
- if selected == "Make":
- if test(cmake,"FLOAT16"):
- st.info("Float16 is selected. You may need to pass compiler specific options for the compiler to recognize the float16 type.")
- mode=st.selectbox("Mode",["txt","MDK","sh","bat"])
- if mode=="txt":
- st.code(textwrap.fill(options(result)))
- if mode=="MDK":
- opts=options(result)
- includes=""
- maybeincludes=re.findall(r'\-I([^\s]+)',opts)
- # Managed in MDK pack file
- #if maybeincludes:
- # includes = maybeincludes
- # st.text("Following include directories must be added")
- # st.code(includes)
- opts=re.sub(r'\-D','',opts)
- opts=re.sub(r'\-I[^\s]+','',opts)
- st.text("MDK Preprocessor Symbols ")
- st.code(opts)
- if mode=="sh":
- lines=options(result).split()
- txt=""
- for l in lines:
- txt += " %s \\\n" % l
- txt += "\n"
- st.code(txt)
- if mode=="bat":
- lines=options(result).split()
- txt=""
- for l in lines:
- txt += " %s ^\n" % l
- txt += "\n"
- st.code(txt)
|