I want to write a function that reads a text file line by line. ie, read the first line until EOL and then treat this as a string, and repeat this until the text ends.
I have managed only this much till now:
fun read file =
let val is = TextIO.openIn file
in
end
You can use TextIO.inputLine : instream -> string option for this. Using refs you can do something like
let
val stream = TextIO.openIn file
val done = ref false
in
while not (!done) do
case TextIO.inputLine stream of
SOME s => print s
| NONE => done := true
end
You can also use the functions in TextIO.StreamIO.
Related
I am writing a short script to read each file in folder of script files and print last word its first line - indicating executable file that is run- ocaml e.g. in usr/bin/ocaml
#use "topfind"
#require "str"
(* fn to print binary file being called from hashbang line of script file *)
let rec myfn (afile) =
print_string (afile^": ");
if (Sys.is_directory afile) then print_endline("This is a directory.");
let ic = (open_in afile) in
if in_channel_length(ic)==0 then print_endline("Zero length file."); exit 0
let line = input_line(ic) in (* error here *)
if (Str.first_chars line 0 = "#") then
let linelist = (Str.split (Str.regexp "/") line) in
let lastword = List.nth linelist ((List.length linelist) - 1) in
print_endline(lastword)
else
print_endline("Not a script file.") ;;
(* to check all files in directory *)
let dir = "." in
let files = Sys.readdir dir in
Array.iter myfn files;;
However, it is giving following error:
File "firstline3.ml", line 13, characters 27-29:
Error: Syntax error
I have tried to replace in with ; or no terminator in this line but it does not help. What is the problem and how can it be solved. Thanks for your help.
You are missing parentheses around the then expression:
if in_channel_length ic =0 then ( print_endline "Zero length file."; exit 0 );
otherwise the expression is read by the parser as
(if in_channel_length ic = 0 then print_endline "Zero length file.");
exit 0;
and the exit is thus unconditional.
Note, you should avoid the physical equality operator, ==, (especially on non-mutable values) and use = instead.
EDIT: If you want to return, you just need to add an else branch
if in_channel_length ic = 0 then print_endline "Zero length file."
else ....
I have written an interpreter using ocamllex and ocamlyacc, the lexer and the parser work correctly but currently they only parse the last .txt argument it receives as oppose to all of them in turn. For example, ./interpret one.txt two.txt three.txt only parses three.txt as oppose to parsing one.txt and then two.txt and then three.txt which is what I want. So for example the parse results are as follows:
one.txt -> "1"
two.txt -> "2"
three.txt -> "3"
On calling ./interpret one.txt two.txt three.txt the current output is: 3 but I want it to be 123
Here is my main class which deals with the stdin and stdout
open Lexer
open Parser
open Arg
open Printf
let toParse c =
try let lexbuf = Lexing.from_channel c in
parser_main lexer_main lexbuf
with Parsing.Parse_error -> failwith "Parse failure!" ;;
let argument = ref stdin in
let prog p = argument := open_in p in
let usage = "./interpreter FILE" in
parse [] prog usage ;
let parsed = toParse !argument in
let result = eval parsed in
let _ = parsed in
flush stdout;
Thanks for your time
There's not really enough code here to be able to help.
If I assume that the output is written by eval, then I see only one call to eval. But there's nothing here that deals with filenames from the command line, so it's hard to say more.
If you are planning to read input from files, then there's no reason to be using stdin for anything as far as I can tell.
(I know this is a very minor point, but this code doesn't constitute a class. Other languages use classes for everything, but this is a module.)
Update
Here's a module that works something like the Unix cat command; it writes out the contents of all the files from the command line one after the next.
let cat () =
for i = 1 to Array.length Sys.argv - 1 do
let ic = open_in Sys.argv.(i) in
let rec loop () =
match input_line ic with
| line -> output_string stdout (line ^ "\n"); loop ()
| exception End_of_file -> ()
in
loop ();
close_in ic
done
let () = cat ()
Here's how it looks when you compile and run it.
$ ocamlc -o mycat mycat.ml
$ echo test line 1 > file1
$ echo test line 2 > file2
$ ./mycat file1 file2
test line 1
test line 2
I have a program where if I type "tac Text.txt" (or any file path in quotations, but this is the test text file I made/used), it will print what is in the txt file, but in reverse. But it doesn't work properly. Where it should appear as
No it is not.
Yes-it is.
No it is not.
Hello my name is Jim.
it appears as
["No it is not."; ""; ""; "Yes-it is."; "No it is not."; "Hello my
name is Jim."].
My code currently is
open System
open System.IO
let countLines path =
File.ReadAllLines(path) |> Seq.toList|> List.rev
// File.ReadAllLines reads the file, turns it into a list then reverses the list.
let printFunction lines =
printfn "%A" lines
// This, when called, will print the file in reverse.
let tac path =
if File.Exists(path) then
let lines = countLines path
printFunction lines
else
printfn "File not found."
[<EntryPoint>]
let main argv =
if argv.Length > 0 then
tac argv.[0]
else
printfn "Error - Please enter file path."
0
I'm assuming it's due to converting to a list, is there a way I can print it normally? I'm hoping it's just a small rookie mistake I've made.
UPDATE: I just changed
let countLines path =
File.ReadAllLines(path) |> Seq.toList|> List.rev
to
let countLines path =
File.ReadAllLines(path) |> Array.rev
Same thing happens, but I'm hoping it leads me closer to the result I want.
It's writing out your entire data type, which is a string list.
You need to iterate through the list:
let printFunction lines =
for line in lines do printfn "%s" line
Print an array, list or seq of lines individually:
let printFunction = Seq.iter (printfn "%s")
open System
//helpfunction
let fileReplace filename needle replace =
let replaceIn (reader:System.IO.StreamReader) needle (replace: String) =
while not(reader.EndOfStream) do
let mutable allText = reader.ReadToEnd()
allText <- allText.Replace(needle, replace)
reader.Close()
//start
let reader = System.IO.File.OpenText filename
let newText = replaceIn reader needle replace
let writer = System.IO.File.CreateText filename
writer.Write newText ; writer.Close()
// testing
let filename = #".\abc.txt"
let needle = "med"
let replace = "MED"
fileReplace filename needle replace
I've experimented a bunch moving reader.Close() around, but with no results yet. I know that if I insert printfn "%A" allText under reader.Close(), it prints the correct result, so I suspect its when I call writer that it goes wrong. I need the code to replace med, with MED in abc.txt. but instead it leaves an empty abc.txt
The replaceIn function that you've written doesn't do anything. Let's separate this out:
let replaceIn (reader:System.IO.StreamReader) needle (replace: string) =
while not(reader.EndOfStream) do
let mutable allText = reader.ReadToEnd()
allText <- allText.Replace(needle, replace)
reader.Close()
First, look at the type of this function:
val replaceIn : reader:System.IO.StreamReader -> needle:string -> replace:string -> unit
The first thing to note is that this function returns unit, that means newText always has a value of (), regardless of file content. That means you are always writing nothing to your file.
Furthermore, your loop is superfluous. You are reading to the end of the stream yet looping until the end of the stream - this loop is unnecessary. There is also no way to observe the results of the loop anyway because the mutable variable you are creating to store the result in is inside the loop.
So, let's look at an alternative:
let fileReplace filename (needle : string) (replace : string) =
let allText = System.IO.File.ReadAllText filename
let newText = allText.Replace(needle, replace)
System.IO.File.WriteAllText(filename, newText)
As you can see, you don't even need to create the readers, you can just the helper functions in System.IO.File.
I know that in order to load a program in OCaml one has to type #use "source_code_file.ml" in toplevel where source_code_file.ml is the file we want to use.
My program reads input from stdin. In the command line i have a txt file that with redirection is used to act as stdin. Can i do this in toplevel? I would like to this because in toplevel i can easily see what type variables have and if things are initialized with the correct values.
If you're on a Unix-like system you can use Unix.dup2 to do almost any kind of input redirection. Here is a function with_stdin that takes an input file name, a function, and a value. It calls the function with standard input redirected from the named file.
let with_stdin fname f x =
let oldstdin = Unix.dup Unix.stdin in
let newstdin = Unix.openfile fname [Unix.O_RDONLY] 0 in
Unix.dup2 newstdin Unix.stdin;
Unix.close newstdin;
let res = f x in
Unix.dup2 oldstdin Unix.stdin;
Unix.close oldstdin;
res
If your function doesn't consume the entire input the leftover input will confuse the toplevel. Here's an example that does consume its entire input:
# let rec linecount c =
try ignore (read_line ()); linecount (c + 1)
with End_of_file -> c;;
val linecount : int -> int = <fun>
# with_stdin "/etc/passwd" linecount 0;;
- : int = 86
#
This technique is too simple if you wanted to interleave interactions with the toplevel with calls to your function to consume just part of its input. I suspect that would make things too complicated to be worth the effort. It would be much easier (and perhaps better overall) to rewrite your code to work with an explicitly specified input channel.