Large text substitutions in Tcl - tcl

Within my Tcl script I'm building source code in another language. Let it be gnuplot source for example. I have Tcl code like this:
# `script' variable contains gnuplot source code
set script {
set terminal pdf
set output "chart.pdf"
set title "[makeTitle]"
plot "$dataFile" using 1:2 title ""
}
# Then I write `script' to file for later execution
Notice that script variable contains command call (makeTitle) and variable substitution (dataFile). The source code itself contains new lines, double quotes.
Question: how can I simply "evaluate" this variable to substitute command calls by their results and variables by their values? Expected result should look like this:
set terminal pdf
set output "chart.pdf"
set title "R(S) Dependence"
plot "r_s.txt" using 1:2 title ""

You're looking for the subst command:
set result [subst $script]

One approach I commonly use in this type of situation is using [string map] with special symbols. For example:
set script {
set terminal pdf
set output "chart.pdf"
set title "%MAKETITLE%"
plot "%DATAFILE%" using 1:2 title ""
}
set script [string map [list %MAKETITLE% [makeTitle] %DATAFILE% $datafile] $script]
While glenn's answer of using [subst] is a good one and will work for the sample code you tested, it can run into issues as the original string gets more complex. Specifically, if it winds up containing characters that Tcl would interpret as commands to run or variables to substitute, you wind up needing to escape them, etc. By using string map and very specific character sequences to replace, you can limit the things that are changed to exactly what you need.

Related

Reading cmd arguments in TCL file

I am trying to run a tcl script through .bat file. I want to read some cmd arguments in the tcl script. Below is my code:
Command to run:
D:\Cadence\Sigrity2021.1\tools\bin\PowerSI.exe -tcl abcd.tcl %new_var%.spd %new_file_name%
Below is how I am trying to read the variable in the tcl file:
sigrity::open document [lindex $argv 0] {!}
It open up the Cadence Sigrity, but I see the below error:
How do I read cmd argument in tcl?
If you have no other way to do it that you can find (and it sounds like that might be the case) then you can fake it by writing a helper file with content like this, filling in the real arguments in the appropriate places:
# Name of script to call
set ::argv0 "abcd.tcl"
# Arguments to pass
set ::argv {}
lappend ::argv "%new_var%.spd"
lappend ::argv "%new_file_name%"
# Number of arguments (rarely used)
set ::argc [llength $::argv]
# Do the call
source $::argv0
Then you can pass that file to PowerSI and it will set things up and chain to the real file. It's messy, but practical.
If you're writing this from Tcl, use the list command to do the quoting of the strings (instead of putting them in double quotes) as it will do exactly the right thing for you. If you're writing the file from another language, you'll want to make sure you put backslashes in before \, ", $ and [ characters. The fiddlyness of doing that depends on your language.

How to get values from JSON file using AppleScript?

In reference to this question,
How to download and get values from JSON file using VBScript or batch file?
how to get the values from JSON file that looks like this,
["AA-BB-CC-MAKE-SAME.json","SS-ED-SIXSIX-TENSE.json","FF-EE-EE-EE-WW.json","ZS-WE-AS-FOUR-MINE.json","DD-RF-LATERS-LATER.json","FG-ER-DC-ED-FG.json"]
using AppleScript in MAC OS?
Here is part of VBScript code in Windows provided by Hackoo,
strJson = http.responseText
Result = Extract(strJson,"(\x22(.*)\x22)")
Arr = Split(Result,",")
For each Item in Arr
wscript.echo Item
Next
'******************************************
Function Extract(Data,Pattern)
Dim oRE,oMatches,Match,Line
set oRE = New RegExp
oRE.IgnoreCase = True
oRE.Global = True
oRE.Pattern = Pattern
set oMatches = oRE.Execute(Data)
If not isEmpty(oMatches) then
For Each Match in oMatches
Line = Line & Trim(Match.Value) & vbCrlf
Next
Extract = Line
End if
End Function
'******************************************
In MAC OS AppleScript I only need the code to get the values of the JSON file to a single array of string values. The above shown example above the VBScript is the how JSON file contents looks like.
Short answer: Unfortunately, AppleScript doesn't provide a built-in feature to parse JSON which is analogous to JavaScript's JSON.parse() method.
Below are a couple of solutions:
Solution 1: Requires a third party plug-in to be installed, which may not always be feasible.
Solution 2: Does not require any third party plug-in to be installed, and instead utilizes tools/features built-in to macOS as standard.
Solution 1:
If you have the luxury of being able to install a third-party plugin on your users systems then you can install JSON Helper for AppleScript (As suggested by #user3439894 in the comments).
Then use it in your AppleScript as follows:
set srcJson to read POSIX file (POSIX path of (path to home folder) & "Desktop/foobar.json")
tell application "JSON Helper" to set myList to read JSON from srcJson
Explanation:
On line 1 we read the contents of the .json file and assign it to the variable named srcJson.
Note You'll need to change the path part (i.e. Desktop/foobar.json) as necessary.
On line 2 we parse the contents using the JSON Helper plug-in. This assigns each item of the source JSON Array to a new AppleScript list. The resultant AppleScript list is assigned to a variable named myList.
Solution 2:
By utilizing tools built-in to macOS as standard, you can also do the following via AppleScript. This assumes that your JSON file is valid and contains a single Array only:
set TID to AppleScript's text item delimiters
set AppleScript's text item delimiters to ","
set myList to text items of (do shell script "tr ''\\\\n\\\\r'' ' ' <~/Desktop/foobar.json | sed 's/^ *\\[ *\"//; s/ *\" *\\] *$//; s/\" *, *\"/,/g;'")
set AppleScript's text item delimiters to TID
Note: you'll need to change the path part (i.e. ~/Desktop/foobar.json) as necessary.
Also, if your .json filename includes a space(s) you'll need to escape them with \\. For instance ~/Desktop/foo\\ bar.json
Explanation:
On line 1 AppleScript's current text item delimiters are assigned to a variable named TID.
On line 2 AppleScript's text item delimiters are set to a comma - this will help when extracting each individual value from the source JSON Array and assigning each value to a new AppleScript list.
On line 3 a shell script is executed via the do shell script command, which performs the following:
Reads the content of the source .json file via the part which reads ~/Desktop/foobar.json. This path currently assumes the file is named foobar.json and resides in your Desktop folder (You'll need to change this path to wherever your actual file exists).
The content of foobar.json is redirected, (note the < before the filepath), to tr (i.e. the part which reads: tr ''\\\\n\\\\r'' ' '). This translation will replace any newline characters which may exists in the contents of the source .json Array with space characters. This ensures the contents of foobar.json is transformed to one line.
Note: A JSON Array can contain newlines between each item and still be valid, so although the example JSON given in your question appears on one line - it is not a requirement of this solution as it will handle multi-line too.
The one line of text is then piped to sed's s command for further processing (i.e. the part which reads: | sed 's/^ *\\[ *\"//; s/ *\" *\\] *$//; s/\" *, *\"/,/g;').
The syntax of the s command is 's/regexp/replacement/flags'.
Let's breakdown each s command to further understand what is happening:
s/^ *\\[ *\"// removes the opening square bracket [, which may be preceded or followed by zero or more space characters, and the following double quote (i.e. the first occurrence) from the beginning of the string.
s/ *\" *\\] *$// removes the closing square bracket ], which may be preceded or followed by zero or more space characters, and the preceding double quote (i.e. the last occurrence) from the end of the string.
s/\" *, *\"/,/g replaces single commas, (which may be preceded with zero or more spaces, and/or followed by zero or more spaces) with a single comma.
The initial part on line 3 which reads; set myList to text items of ... utilizes text items to read the String into an AppleScript list using commas as delimiters to determine each item of the list. The resultant Array is assigned to a variable named myList.
On line 4 AppleScript's text item delimiters are restored to their original value.
Utilizing a variable for the source JSON filepath.
If you want to utilize a variable for the filepath to the source .json file then you can do something like this instead:
set srcFilePath to quoted form of (POSIX path of (path to home folder) & "Desktop/foobar.json")
set TID to AppleScript's text item delimiters
set AppleScript's text item delimiters to ","
set myList to text items of (do shell script "tr ''\\\\n\\\\r'' ' ' <" & srcFilePath & " | sed 's/^ *\\[ *\"//; s/ *\" *\\] *$//; s/\" *, *\"/,/g;'")
set AppleScript's text item delimiters to TID
Note This is very much the same as the first example. The notable differences are:
On the first line we assign the filepath to a variable named srcFilePath.
In the do shell script we reference the srcFilePath variable.
Additional note regarding JSON escaped special characters: Solution 2 preserves any JSON escaped special characters which may be present in the values of source JSON array. However, Solution 1 will interpret them.
Caveats Solution 2 produces unexpected results when an item in the source JSON array includes a comma because a comma is used as a text item delimiters.
How to get the values from JSON file that looks like this,
["AA-BB-CC-MAKE-SAME.json","SS-ED-SIXSIX-TENSE.json","FF-EE-EE-EE-WW.json","ZS-WE-AS-FOUR-MINE.json","DD-RF-LATERS-LATER.json","FG-ER-DC-ED-FG.json"]
If you actually mean what you wrote, and that the contents of the JSON file is that list of six strings in a single array, formatted on a single line, the simplest way is to treat it as text, trim the opening and closing square brackets, then delimit its fields at every occurrence of a ,. Finally, each individual text item can have the surrounding quotes trimmed as well.
Examining the VBScript, it looks like it uses a very similar process, albeit with regular expressions, which AppleScript doesn't feature but which aren't especially necessary in this simple situation.
Let's assume that the JSON array above is stored in a file on your desktop called "myfile.json". Then:
set home to the path to home folder
set f to the POSIX path of home & "Desktop/myfile.json"
set JSONstr to read POSIX file f
# Trim square brackets
set JSONstr to text 2 thru -2 of JSONstr
# Delimit text fields using comma
set the text item delimiters to ","
set Arr to the text items of JSONstr
# Trim quotes of each item in Arr
repeat with a in Arr
set contents of a to text 2 thru -2 of a
end repeat
# The final array
Arr
I only need the code to get the values of the JSON file to a single array of string values. The above shown example above the VBScript is the how JSON file contents looks like.
The variable Arr now contains the array (referred to as lists in AppleScript) of string values. You can access a particular item in it like this:
item 2 of Arr --> "SS-ED-SIXSIX-TENSE.json"
A More General Solution
I've decided to include a more advanced way to handle JSON in an AppleScript, partly because I've been doing a lot of JSON processing quite recently and this is all fresh on my event horizon; but also to demonstrate that, using AppleScriptObjC, parsing even very complex JSON data is not only possible, but quite simple.
I don't think you'll need it in this specific case, but it could come in useful for some future situation.
The script has three sections: it starts off importing the relevant Objective-C framework that gives AppleScript additional powers; then, I define the actual handler itself, called JSONtoRecord, which I describe below. Lastly, comes the bottom of the script where you can enter your code and do whatever you like with it:
use framework "Foundation"
use scripting additions
--------------------------------------------------------------------------------
property ca : a reference to current application
property NSData : a reference to ca's NSData
property NSDictionary : a reference to ca's NSDictionary
property NSJSONSerialization : a reference to ca's NSJSONSerialization
property NSString : a reference to ca's NSString
property NSUTF8StringEncoding : a reference to 4
--------------------------------------------------------------------------------
on JSONtoRecord from fp
local fp
set JSONdata to NSData's dataWithContentsOfFile:fp
set [x, E] to (NSJSONSerialization's ¬
JSONObjectWithData:JSONdata ¬
options:0 ¬
|error|:(reference))
if E ≠ missing value then error E
tell x to if its isKindOfClass:NSDictionary then ¬
return it as record
x as list
end JSONtoRecord
--------------------------------------------------------------------------------
###YOUR CODE BELOW HERE
#
#
set home to the path to home folder
set f to the POSIX path of home & "Desktop/myfile.json"
JSONtoRecord from f
--> {"AA-BB-CC-MAKE-SAME.json", "SS-ED-SIXSIX-TENSE.json", ¬
--> "FF-EE-EE-EE-WW.json", "ZS-WE-AS-FOUR-MINE.json", ¬
--> "DD-RF-LATERS-LATER.json", "FG-ER-DC-ED-FG.json"}
At the bottom of the script, I've called the JSONtoRecord handler, passing it the location of myfile.json. One of the benefits of this handler is that it doesn't matter whether the file is formatted all on one line, or over many lines. It can also handle complex, nested JSON arrays.
In those instances, what it returns is a native AppleScript record object, with all the JSON variables stored as property values in the record. Accessing the variables then becomes very simple.
This is actually exactly what the JSON Helper application that a couple of people have already mentioned does under the hood.
The one criterion (other than the JSON file containing valid JSON data) is that the path to the file is a posix path written in full, e.g. /Users/CK/Desktop/myfile.json, and not ~/Desktop/myfile.json or, even worse, Macintosh HD:Users:CK:Desktop:myfile.json.

How to grep parameters inside square brackets?

Could you please help me with the following script?
It is a Tcl script which Synopsys IC Compiler II will source.
set_dont_use [get_lib_cells */*CKGT*0P*] -power
set_dont_use [get_lib_cells */*CKTT*0P*] -setup
May I know how to take only */*CKGT*0P* and */*CKTT*0P* and assign these to a variable.
Of course you can treat a Tcl script as something you search through; it's just a file with text in it after all.
Let's write a script to select the text out. It'll be a Tcl script, of course. For readability, I'm going to put the regular expression itself in a global variable; treat it like a constant. (In larger scripts, I find it helps a lot to give names to REs like this, as those names can be used to remind me of the purpose of the regular expression. I'll call it “RE” here.)
set f [open theScript.tcl]
# Even with 10 million lines, modern computers will chew through it rapidly
set lines [split [read $f] "\n"]
close $f
# This RE will match the sample lines you've told us about; it might need tuning
# for other inputs (and knowing what's best is part of the art of RE writing)
set RE {^set_dont_use \[get_lib_cells ([\w*/]+)\] -\w+$}
foreach line $lines {
if {[regexp $RE $line -> term]} {
# At this point, the part you want is assigned to $term
puts "FOUND: $term"
}
}
The key things in the RE above? It's in braces to reduce backslash-itis. Literal square brackets are backslashed. The bit in parentheses is the bit we're capturing into the term variable. [\w*/]+ matches a sequence of one or more characters from a set consisting of “standard word characters” plus * and /.
The use of regexp has -> as a funny name for a variable that is ignored. I could have called it dummy instead; it's going to have the whole matched string in it when the RE matches, but we already have that in $term as we're using a fully-anchored RE. But I like using -> as a mnemonic for “assign the submatches into these”. Also, the formal result of regexp is the number of times the RE matched; without the -all option, that's effectively a boolean that is true exactly when there was a match, which is useful. Very useful.
To assign the output of any command <command> to a variable with a name <name>, use set <name> [<command>]:
> set hello_length [string length hello]
5
> puts "The length of 'hello' is $hello_length."
The length of 'hello' is 5.
In your case, maybe this is what you want? (I still don't quite understand the question, though.)
set ckgt_cells [get_lib_cells */*CKGT*0P*]
set cktt_cells [get_lib_cells */*CKTT*0P*]

Printing variables including functions from Makefile and/or variable introspection

If you iterate over .VARIABLES and print each, any true variable can be printed correctly with the following rule:
print_variables: $(foreach V,$(.VARIABLES),print-$(V)) .phony_explicit
print-%: .phony_explicit; #echo "$* = \"$($*)\""
.PHONY: .phony_explicit ...
A 0- or 1-line function will still work, but any more will result in Syntax error: Unterminated quote string. Just one multiline function will break the entire print_variables rule. As a workaround, I have added ;\ to each line in my function definitions, but that won't fix existing multiline functions (either via includes from this makefile or via other makefiles including this one.) What can I do? Is there a container of just function variables, or a way to test if a variable is a function definition?
A simple minimal example would be easier to understand; this has nothing to do with .VARIABLES, pattern rules, etc. (and I'm not sure what the point of the .phony_explicit prereq is..)
define F
foo
bar
endef
print: ; echo "F = $(F)"
will show the problem:
echo "F = foo
/bin/sh: 1: Syntax error: Unterminated quoted string
This is because when make sees a variable that contains newlines in a recipe, it assumes that the newlines mean you want the lines of the variable to become lines in the recipe.
First in general you should use single-quotes around strings you send to the shell, unless you need the shell to expand them; it won't help in this situation but in general it's much safer.
There's no way to undo that, really. You have a number of options.
The first is to not use echo but instead use the make function info:
print-F: ; $(info F = "$(F)")
yields:
F = "foo
bar"
Another option is to use subst to replace the newlines with some other value. The new value cannot itself contain explicit newlines, but you can ask the shell to print a newline for you:
# Create a variable containing a single newline
# Note this must contain TWO newlines!
define NL
endef
print-F: printf 'F = "$(subst %,%%,$(subst $(NL),\n,$(F))"\n'
Yields:
printf 'F = "foo\nbar"\n'
F = "foo
bar"
One final option is to convert your makefile to use the .ONESHELL feature, but I assume that's a step too far just to get this debugging output available :).

How to keep commands quiet in TCL?

How to execute set command without printing output on the screen? I want to read a file without displaying the contents on the screen.
set a [open "giri.txt" r]
set b [read $ifile]
What you're observing is just the standard behaviour of an interactive Tcl shell: each Tcl command returns a result value, and a return code. If the Tcl shell is interactive (that is, its input and output streams are connected to a terminal), after executing each command, the string representation of the result value the command returned is printed, and then the prompt is shown again. If the shell is not interactive, no results are printed and no prompt is shown.
(On a side note, such behaviour is ubiquitous with interpreters — various Unix shells, Python and Ruby interpreters do the same thing.)
If you want to inhibit such printouts in an interactive session (comes in handy from time to time), a simple hack to achieve that is to chain the command you want to "silence" with a "silent" command (producing a value whose string representation is an empty string), for instance:
set a [open "giri.txt" r]; list
Here, the list returned by the list command having no arguments is an empty list whose string representation is an empty string. In an interactive shell, this chain of commands will output literally nothing.
It bears repeating that such a hack might only ever be needed in an interactive session — do not use it in scripts.
In Mentor ModelSim Tcl it is possible to do:
quietly set answer 42
Also in Mentor Questa:
help quietly
The quietly command turns off transcript echoing for the specified command.
You can turn this off in an interactive tclsh
set tcl_interactive false
but that will also turn off the prompt.